From b153d6071cfd8a881fcbe6c1c16e12f1cb4bf5e2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 29 Sep 2026 12:17:02 +0100 Subject: [PATCH] Support `bit_set[E; [4]u64]` style types --- base/runtime/core_builtin.odin | 13 +++- core/fmt/fmt.odin | 60 ++++++++++++++++ src/check_expr.cpp | 4 -- src/check_type.cpp | 13 ++-- src/llvm_backend_const.cpp | 40 ++++++++++- src/llvm_backend_expr.cpp | 127 +++++++++++++++++++++++++++++++-- 6 files changed, 236 insertions(+), 21 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index fccc8b1df..df8a36ff6 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -1652,7 +1652,18 @@ when MAP_ENABLED { // `card` returns the number of bits that are set in a bit_set—its cardinality @builtin card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int { - return int(intrinsics.count_ones(transmute(intrinsics.type_bit_set_underlying_type(S))s)) + Backing :: intrinsics.type_bit_set_underlying_type(S) + when intrinsics.type_is_array(Backing) { + // bit_set backed by an array of integers: sum the population count of each element + backing := transmute(Backing)s + count := 0 + for elem in backing { + count += int(intrinsics.count_ones(elem)) + } + return count + } else { + return int(intrinsics.count_ones(transmute(Backing)s)) + } } diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 53cda51f4..a2e6d2247 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1815,6 +1815,14 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') { fmt_bit_set(fi, val, info.name, verb) case runtime.Type_Info_Bit_Set: + if info.underlying != nil { + #partial switch _ in runtime.type_info_base(info.underlying).variant { + case runtime.Type_Info_Array: + fmt_bit_set_array(fi, v, type_info, name, verb) + return + } + } + bits: u128 bit_size := u128(8*type_info.size) @@ -1914,6 +1922,58 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') { } } +// Formats an array-of-integers backed bit_set (e.g. `bit_set[E; [4]u64]`). +// The bits are stored as a little-endian sequence in memory (bit `b` is bit `b%8` of byte `b/8`), +// so they can be scanned directly regardless of how many array elements back the set. +fmt_bit_set_array :: proc(fi: ^Info, v: any, type_info: ^runtime.Type_Info, name: string, verb: rune) { + info := type_info.variant.(runtime.Type_Info_Bit_Set) + bit_size := int(8*type_info.size) + + et := runtime.type_info_base(info.elem) + e, is_enum := et.variant.(runtime.Type_Info_Enum) + + if verb != 'w' { + if name != "" { + io.write_string(fi.writer, name, &fi.n) + } else { + reflect.write_type(fi.writer, type_info, &fi.n) + } + } + io.write_byte(fi.writer, '{', &fi.n) + defer io.write_byte(fi.writer, '}', &fi.n) + + bytes := ([^]u8)(v.data) + commas := 0 + loop: for bit_index in 0..> uint(bit_index & 7)) & 1 == 0 { + continue + } + i := i64(bit_index) + info.lower + if commas > 0 { + io.write_string(fi.writer, ", ", &fi.n) + } + if is_enum { + enum_name: string + if ti_named, is_named := info.elem.variant.(runtime.Type_Info_Named); is_named { + enum_name = ti_named.name + } + for ev, evi in e.values { + if u64(ev) == u64(i) { + if verb == 'w' { + io.write_string(fi.writer, enum_name, &fi.n) + io.write_byte(fi.writer, '.', &fi.n) + } + io.write_string(fi.writer, e.names[evi], &fi.n) + commas += 1 + continue loop + } + } + } + io.write_i64(fi.writer, i, 10, &fi.n) + commas += 1 + } +} + // Writes the specified number of indents to the provided Info structure // // Inputs: diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ec812bac1..5d0c3df13 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -11707,10 +11707,6 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * field_count = et->Enum.fields.count; } - if (is_type_array(bit_set_to_int(t))) { - is_constant = false; - } - for (Ast *elem : cl->elems) { if (elem->kind == Ast_FieldValue) { error(elem, "'field = value' in a bit_set literal is not allowed"); diff --git a/src/check_type.cpp b/src/check_type.cpp index cb99bb4d9..696ae21ab 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1384,14 +1384,11 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t Type *t = default_type(lhs.type); if (bs->underlying != nullptr) { Type *u = check_type(c, bs->underlying); - // if (!is_valid_bit_field_backing_type(u)) { - if (!is_type_integer(u)) { + if (!is_valid_bit_field_backing_type(u)) { gbString ts = type_to_string(u); - error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts); + error(bs->underlying, "Expected an underlying integer or array of integers for the bit set, got %s", ts); gb_string_free(ts); - if (!is_valid_bit_field_backing_type(u)) { - return; - } + return; } type->BitSet.underlying = u; } @@ -1502,9 +1499,9 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t i64 bits = MAX_BITS ; if (bs->underlying != nullptr) { Type *u = check_type(c, bs->underlying); - if (!is_type_integer(u)) { + if (!is_valid_bit_field_backing_type(u)) { gbString ts = type_to_string(u); - error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts); + error(bs->underlying, "Expected an underlying integer or array of integers for the bit set, got %s", ts); gb_string_free(ts); return; } diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 31d1e16fb..d9f7d51be 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -500,7 +500,45 @@ gb_internal LLVMValueRef lb_big_int_to_llvm(lbModule *m, Type *original_type, Bi if (big_int_is_zero(a)) { return LLVMConstNull(lb_type(m, original_type)); } - + + // NOTE(bill): a bit_set backed by an array of integers (e.g. `bit_set[E; [4]u64]`) is represented as an LLVM array. + // There for its constant value is the same bit mask as an integer-backed bit_set, split into element-sized + // little-endian chunks (element `i` holds bits `[i*elem_bits, (i+1)*elem_bits)`), which matches how membership + // and literals index into the array. + if (is_type_bit_set(original_type)) { + Type *backing = bit_set_to_int(original_type); + if (is_type_array(backing)) { + Type *elem = backing->Array.elem; + i64 n = backing->Array.count; + i64 elem_bits = 8*type_size_of(elem); + + BigInt v = {}; + big_int_init(&v, a); + defer (big_int_dealloc(&v)); + + BigInt shift = {}; + big_int_from_u64(&shift, cast(u64)elem_bits); + defer (big_int_dealloc(&shift)); + + BigInt mask = {}; // (1 << elem_bits) - 1 + big_int_from_u64(&mask, 1); + big_int_shl(&mask, &mask, &shift); + mp_decr(&mask); + defer (big_int_dealloc(&mask)); + + LLVMTypeRef elem_llvm = lb_type(m, elem); + LLVMValueRef *elems = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)n); + for (i64 i = 0; i < n; i++) { + BigInt chunk = {}; + big_int_and(&chunk, &v, &mask); + elems[i] = lb_big_int_to_llvm(m, elem, &chunk); + big_int_dealloc(&chunk); + big_int_shr_eq(&v, &shift); + } + return LLVMConstArray(elem_llvm, elems, cast(unsigned)n); + } + } + BigInt val = {}; big_int_init(&val, a); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 8348041b4..19bcefc5e 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -187,6 +187,10 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, if (is_type_bit_set(x.type)) { ExactValue ev_mask = exact_bit_set_all_set_mask(x.type); lbValue mask = lb_const_value(p->module, x.type, ev_mask); + if (is_type_array(bit_set_to_int(x.type))) { + lbValue res = lb_emit_arith(p, Token_Xor, x, mask, x.type); + return lb_emit_conv(p, res, type); + } cmp.value = LLVMBuildXor(p->builder, x.value, mask.value, ""); } else { cmp.value = LLVMBuildNot(p->builder, x.value, ""); @@ -2116,6 +2120,36 @@ gb_internal lbValue lb_build_binary_in(lbProcedure *p, lbValue left, lbValue rig Type *it = bit_set_to_int(rt); + if (is_type_array(it)) { + Type *elem = base_array_type(it); + i64 elem_bits = 8*type_size_of(elem); + + lbValue key = lb_emit_conv(p, left, t_int); + lbValue lower_v = lb_const_int(p->module, t_int, rt->BitSet.lower); + key = lb_emit_arith(p, Token_Sub, key, lower_v, t_int); + + lbValue ebits = lb_const_int(p->module, t_int, elem_bits); + lbValue elem_idx = lb_emit_arith(p, Token_Quo, key, ebits, t_int); + lbValue bit_idx = lb_emit_arith(p, Token_Mod, key, ebits, t_int); + + lbValue arr = lb_emit_transmute(p, right, it); + lbValue arr_ptr = lb_address_from_load_or_generate_local(p, arr); + arr_ptr.type = alloc_type_pointer(it); + + lbValue e_ptr = lb_emit_array_ep(p, arr_ptr, elem_idx); + lbValue e_val = lb_emit_load(p, e_ptr); + + lbValue shifted = lb_emit_arith(p, Token_Shr, e_val, lb_emit_conv(p, bit_idx, elem), elem); + lbValue masked = lb_emit_arith(p, Token_And, shifted, lb_const_int(p->module, elem, 1), elem); + lbValue zero = lb_const_int(p->module, elem, 0); + + if (op == Token_in) { + return lb_emit_conv(p, lb_emit_comp(p, Token_NotEq, masked, zero), t_bool); + } else { + return lb_emit_conv(p, lb_emit_comp(p, Token_CmpEq, masked, zero), t_bool); + } + } + left = lb_emit_conv(p, left, it); if (is_type_different_to_arch_endianness(it)) { left = lb_emit_byte_swap(p, left, integer_endian_type_to_platform_type(it)); @@ -3429,6 +3463,29 @@ gb_internal lbValue lb_compare_records(lbProcedure *p, TokenKind op_kind, lbValu +gb_internal lbValue lb_bit_set_array_is_zero(lbProcedure *p, lbValue arr) { + Type *at = base_type(arr.type); + GB_ASSERT(at->kind == Type_Array); + Type *elem = at->Array.elem; + i64 n = at->Array.count; + + lbValue acc = {}; + acc.type = elem; + acc.value = LLVMBuildExtractValue(p->builder, arr.value, 0, ""); + for (i64 i = 1; i < n; i++) { + lbValue e = {}; + e.type = elem; + e.value = LLVMBuildExtractValue(p->builder, arr.value, cast(unsigned)i, ""); + + acc = lb_emit_arith(p, Token_Or, acc, e, elem); + } + + lbValue res = {}; + res.type = t_llvm_bool; + res.value = LLVMBuildICmp(p->builder, LLVMIntEQ, acc.value, lb_const_int(p->module, elem, 0).value, ""); + return res; +} + gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right) { Type *a = core_type(left.type); Type *b = core_type(right.type); @@ -3753,6 +3810,52 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left } if (is_type_bit_set(a)) { + Type *it_backing = bit_set_to_int(a); + if (is_type_array(it_backing)) { + lbValue lhs = lb_emit_transmute(p, left, it_backing); + lbValue rhs = lb_emit_transmute(p, right, it_backing); + switch (op_kind) { + case Token_CmpEq: + case Token_NotEq: + { + lbValue diff = lb_emit_arith(p, Token_Xor, lhs, rhs, it_backing); + lbValue equal = lb_bit_set_array_is_zero(p, diff); + if (op_kind == Token_NotEq) { + lbValue res = {}; + res.type = t_llvm_bool; + res.value = LLVMBuildNot(p->builder, equal.value, ""); + return res; + } + return equal; + } + case Token_Lt: + case Token_LtEq: + { + // subset: (lhs &~ rhs) == {}; strict also requires lhs != rhs + lbValue d = lb_emit_arith(p, Token_AndNot, lhs, rhs, it_backing); + lbValue res = lb_bit_set_array_is_zero(p, d); + if (op_kind == Token_Lt) { + lbValue diff = lb_emit_arith(p, Token_Xor, lhs, rhs, it_backing); + lbValue equal = lb_bit_set_array_is_zero(p, diff); + res = lb_emit_arith(p, Token_AndNot, res, equal, t_llvm_bool); + } + return res; + } + case Token_Gt: + case Token_GtEq: + { + // superset: (rhs &~ lhs) == {}; strict also requires lhs != rhs + lbValue d = lb_emit_arith(p, Token_AndNot, rhs, lhs, it_backing); + lbValue res = lb_bit_set_array_is_zero(p, d); + if (op_kind == Token_Gt) { + lbValue diff = lb_emit_arith(p, Token_Xor, lhs, rhs, it_backing); + lbValue equal = lb_bit_set_array_is_zero(p, diff); + res = lb_emit_arith(p, Token_AndNot, res, equal, t_llvm_bool); + } + return res; + } + } + } switch (op_kind) { case Token_Lt: case Token_LtEq: @@ -6461,20 +6564,30 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { Type *backing = bit_set_to_int(type); if (is_type_array(backing)) { - GB_PANIC("TODO: bit_set [N]T"); Type *base_it = core_array_type(backing); i64 bits_per_elem = 8*type_size_of(base_it); - gb_unused(bits_per_elem); - lbValue one = lb_const_value(p->module, t_i64, exact_value_i64(1)); + lbValue ebits = lb_const_value(p->module, t_int, exact_value_i64(bits_per_elem)); + lbValue one = lb_const_value(p->module, base_it, exact_value_i64(1)); + + lbValue arr_ptr = lb_addr_get_ptr(p, v); + arr_ptr.type = alloc_type_pointer(backing); + for (Ast *elem : cl->elems) { GB_ASSERT(elem->kind != Ast_FieldValue); lbValue expr = lb_build_expr(p, elem); GB_ASSERT(expr.type->kind != Type_Tuple); - lbValue e = lb_emit_conv(p, expr, t_i64); - e = lb_emit_arith(p, Token_Sub, e, lower, t_i64); - // lbValue idx = lb_emit_arith(p, Token_Div, e, bits_per_elem, t_i64); - // lbValue val = lb_emit_arith(p, Token_Div, e, bits_per_elem, t_i64); + lbValue e = lb_emit_conv(p, expr, t_int); + e = lb_emit_arith(p, Token_Sub, e, lower, t_int); + + lbValue elem_idx = lb_emit_arith(p, Token_Quo, e, ebits, t_int); + lbValue bit_idx = lb_emit_arith(p, Token_Mod, e, ebits, t_int); + + lbValue e_ptr = lb_emit_array_ep(p, arr_ptr, elem_idx); + lbValue bit = lb_emit_arith(p, Token_Shl, one, lb_emit_conv(p, bit_idx, base_it), base_it); + lbValue old_value = lb_emit_load(p, e_ptr); + lbValue new_value = lb_emit_arith(p, Token_Or, old_value, bit, base_it); + lb_emit_store(p, e_ptr, new_value); } } else { Type *it = bit_set_to_int(bt);