diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 5dee0cda4..fa33d6c01 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -100,6 +100,7 @@ gb_internal Type * check_init_variable (CheckerContext *c, Entity * gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size=0); +gb_internal bool check_is_expressible(CheckerContext *ctx, Operand *o, Type *type); gb_internal void add_map_key_type_dependencies(CheckerContext *ctx, Type *key); gb_internal Type *make_soa_struct_fixed(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem, i64 count, Type *generic_type); @@ -1182,6 +1183,14 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ return; } + // a bit_field field's width is on its entity, not its type + if (c->bit_field_bit_size != 0 && operand->mode == Addressing_Constant && is_type_typed(operand->type)) { + check_is_expressible(c, operand, type); + if (operand->mode == Addressing_Invalid) { + return; + } + } + if (operand->mode == Addressing_ProcGroup) { bool good = false; if (type != nullptr && is_type_proc(type)) { diff --git a/tests/internal/test_bit_field.odin b/tests/internal/test_bit_field.odin new file mode 100644 index 000000000..40000b16e --- /dev/null +++ b/tests/internal/test_bit_field.odin @@ -0,0 +1,173 @@ +package test_internal + +import "base:intrinsics" +import "core:testing" + +// A `bit_field`'s layout is defined on the backing *value*, not on its bytes: the first field +// occupies the low bits and each next one continues upward, so for +// `bit_field u32 { a: bool|1, b: bool|1, f: u32|30 }` and a backing `x`, `a` is `x & 1`, `b` is +// `(x >> 1) & 1` and `f` is `x >> 2`. Writing the tests against the backing integer rather than a +// byte array is what keeps them endian-neutral +@(test) +bit_field_layout_starts_at_the_low_bit :: proc(t: ^testing.T) { + BF :: bit_field u32 { a: bool | 1, b: bool | 1, f: u32 | 30 } + + backings := []u32{0, 1, 2, 3, 0b1101, 0xFFFF_FFFF, 0x8000_0000, 0xDEAD_BEEF} + for x in backings { + v := transmute(BF)x + testing.expect_value(t, v.a, x & 1 == 1) + testing.expect_value(t, v.b, (x >> 1) & 1 == 1) + testing.expect_value(t, v.f, x >> 2) + testing.expect_value(t, transmute(u32)v, x) + } + + // and the same layout when built field by field rather than transmuted into + w: BF + w.a = true + w.b = false + w.f = 0x3FFF_FFFF + testing.expect_value(t, transmute(u32)w, 0xFFFF_FFFD) +} + +// the declared widths, and offsets that are their running sum +@(test) +bit_field_declared_sizes_and_offsets :: proc(t: ^testing.T) { + BF :: bit_field u32 { a: u8 | 3, b: u16 | 9, c: bool | 1, d: u8 | 7 } // 12 bits spare + + testing.expect_value(t, intrinsics.type_field_bit_size(BF, "a"), 3) + testing.expect_value(t, intrinsics.type_field_bit_size(BF, "b"), 9) + testing.expect_value(t, intrinsics.type_field_bit_size(BF, "c"), 1) + testing.expect_value(t, intrinsics.type_field_bit_size(BF, "d"), 7) + + testing.expect_value(t, intrinsics.type_field_bit_offset(BF, "a"), 0) + testing.expect_value(t, intrinsics.type_field_bit_offset(BF, "b"), 3) + testing.expect_value(t, intrinsics.type_field_bit_offset(BF, "c"), 12) + testing.expect_value(t, intrinsics.type_field_bit_offset(BF, "d"), 13) + + testing.expect_value(t, size_of(BF), size_of(u32)) + + // a field that straddles a byte boundary is still contiguous in the value + v: BF + v.b = 0b1_1111_1111 + testing.expect_value(t, transmute(u32)v, 0b1_1111_1111 << 3) + testing.expect_value(t, v.b, 0b1_1111_1111) +} + +// a write touches its own bits and no others, including the spare high bits of the backing +@(test) +bit_field_writes_leave_neighbours_alone :: proc(t: ^testing.T) { + P :: bit_field u16 { a: u8 | 3, b: u8 | 2 } // 11 bits spare + + p := transmute(P)u16(0xFFFF) + p.a = 0 + testing.expect_value(t, transmute(u16)p, 0xFFF8) + testing.expect_value(t, p.b, 3) + + q: P + q.b = 3 + testing.expect_value(t, transmute(u16)q, 0b11000) + testing.expect_value(t, q.a, 0) + + q.a = 5 + testing.expect_value(t, q.b, 3) + testing.expect_value(t, q.a, 5) +} + +// a signed field is sign extended from its own width, not from its type's +@(test) +bit_field_signed_fields_sign_extend :: proc(t: ^testing.T) { + S :: bit_field u8 { s: i8 | 3, r: u8 | 5 } + + expected := [8]i8{0, 1, 2, 3, -4, -3, -2, -1} + for v in u8(0) ..< 8 { + x := transmute(S)v + testing.expect_value(t, x.s, expected[v]) + } + + W :: bit_field u32 { s: i32 | 12, r: u32 | 20 } + w: W + w.s = -1 + testing.expect_value(t, w.s, -1) + testing.expect_value(t, transmute(u32)w, 0xFFF) + w.s = -2048 + testing.expect_value(t, w.s, -2048) + w.s = 2047 + testing.expect_value(t, w.s, 2047) +} + +// A 1-bit boolean field is well formed at every backing value: the mask leaves only bit 0, so the +// read is 0 or 1 whichever way it is tested. Wider boolean fields are legal -- any non-zero value +// is true -- and are not covered here +@(test) +bit_field_boolean_field_is_well_formed :: proc(t: ^testing.T) { + One :: bit_field u8 { flag: bool | 1, rest: u8 | 7 } + + for i in 0 ..< 256 { + backing := u8(i) + bytes := [1]u8{backing} + o := (^One)(&bytes[0])^ + f := o.flag + testing.expectf(t, f == true || f == false, "backing %v gave a bool that is neither", backing) + testing.expect_value(t, f, backing & 1 == 1) + testing.expect_value(t, o.rest, backing >> 1) + + // and it survives being copied out of the bit_field + g := f + testing.expect_value(t, g, f) + testing.expect_value(t, u8(f), backing & 1) + } +} + +// a constant is range-checked against the field's width whether or not it carries its type +@(test) +bit_field_typed_constants_in_range :: proc(t: ^testing.T) { + E :: enum u8 { A, B, C, D } + W :: bit_field u8 { n: u8 | 2, s: i8 | 2, e: E | 1, r: u8 | 3 } + + w: W + w.n = u8(3) + w.s = i8(-2) + w.e = .B + w.r = 7 + testing.expect_value(t, w.n, 3) + testing.expect_value(t, w.s, -2) + testing.expect_value(t, w.e, E.B) + testing.expect_value(t, w.r, 7) + + C :: u8(3) + v := W{ n = C, s = i8(1), e = .A, r = u8(5) } + testing.expect_value(t, v.n, 3) + testing.expect_value(t, v.s, 1) + testing.expect_value(t, v.e, E.A) + testing.expect_value(t, v.r, 5) + + // an enum field holds every value its width can represent + V :: bit_field u8 { e: E | 2, r: u8 | 6 } + for want in ([]E{.A, .B, .C, .D}) { + x: V + x.e = want + testing.expect_value(t, x.e, want) + } + + // a variable is not a constant, so it truncates rather than being refused + m: u8 = 200 + w.n = m + testing.expect_value(t, w.n, 0) +} + +// a field as wide as its backing keeps every value +@(test) +bit_field_full_width_fields :: proc(t: ^testing.T) { + Full :: bit_field u64 { v: u64 | 64 } + Half :: bit_field u64 { lo: u32 | 32, hi: u32 | 32 } + + f := transmute(Full)u64(0xDEAD_BEEF_CAFE_F00D) + testing.expect_value(t, f.v, 0xDEAD_BEEF_CAFE_F00D) + + h := transmute(Half)u64(0xDEAD_BEEF_CAFE_F00D) + testing.expect_value(t, h.lo, 0xCAFE_F00D) + testing.expect_value(t, h.hi, 0xDEAD_BEEF) + + h.hi = 0 + testing.expect_value(t, transmute(u64)h, 0xCAFE_F00D) +}