const std = @import("std"); fn PtrCastPreserveCV(comptime T: type, comptime PtrToT: type, comptime NewT: type) type { return switch (PtrToT) { *T => *NewT, *const T => *const NewT, *volatile T => *volatile NewT, *const volatile T => *const volatile NewT, else => @compileError("wtf you doing"), }; } fn BitType(comptime FieldType: type, comptime ValueType: type, comptime shamt: usize) type { const self_bit: FieldType = (1 << shamt); return extern struct { bits: Bitfield(FieldType, shamt, 1), pub fn set(self: anytype) void { self.bits.field().* |= self_bit; } pub fn unset(self: anytype) void { self.bits.field().* &= ~self_bit; } pub fn read(self: anytype) ValueType { return @bitCast(@as(u1, @truncate(self.bits.field().* >> shamt))); } // Since these are mostly used with MMIO, I want to avoid // reading the memory just to write it again, also races pub fn write(self: anytype, val: ValueType) void { if (@as(bool, @bitCast(val))) { self.set(); } else { self.unset(); } } }; } // Original Bit Constructor // pub fn Bit(comptime FieldType: type, comptime shamt: usize) type { // return BitType(FieldType, u1, shamt); // } pub fn Bit(comptime FieldType: type, comptime shamt: usize) type { return BitType(FieldType, bool, shamt); } fn Boolean(comptime FieldType: type, comptime shamt: usize) type { return BitType(FieldType, bool, shamt); } pub fn Bitfield(comptime FieldType: type, comptime shamt: usize, comptime num_bits: usize) type { if (shamt + num_bits > @bitSizeOf(FieldType)) { @compileError("bitfield doesn't fit"); } const self_mask: FieldType = ((1 << num_bits) - 1) << shamt; const ValueType = std.meta.Int(.unsigned, num_bits); return extern struct { dummy: FieldType, fn field(self: anytype) PtrCastPreserveCV(@This(), @TypeOf(self), FieldType) { return @ptrCast(self); } pub fn write(self: anytype, val: ValueType) void { self.field().* &= ~self_mask; self.field().* |= @as(FieldType, @intCast(val)) << shamt; } pub fn read(self: anytype) ValueType { const val: FieldType = self.field().*; return @intCast((val & self_mask) >> shamt); } }; } test "bit" { const S = extern union { low: Bit(u32, 0), high: Bit(u32, 1), val: u32, }; std.testing.expect(@sizeOf(S) == 4); std.testing.expect(@bitSizeOf(S) == 32); var s: S = .{ .val = 1 }; std.testing.expect(s.low.read() == 1); std.testing.expect(s.high.read() == 0); s.low.write(0); s.high.write(1); std.testing.expect(s.val == 2); } test "boolean" { const S = extern union { low: Boolean(u32, 0), high: Boolean(u32, 1), val: u32, }; std.testing.expect(@sizeOf(S) == 4); std.testing.expect(@bitSizeOf(S) == 32); var s: S = .{ .val = 2 }; std.testing.expect(s.low.read() == false); std.testing.expect(s.high.read() == true); s.low.write(true); s.high.write(false); std.testing.expect(s.val == 1); } test "bitfield" { const S = extern union { low: Bitfield(u32, 0, 16), high: Bitfield(u32, 16, 16), val: u32, }; std.testing.expect(@sizeOf(S) == 4); std.testing.expect(@bitSizeOf(S) == 32); var s: S = .{ .val = 0x13376969 }; std.testing.expect(s.low.read() == 0x6969); std.testing.expect(s.high.read() == 0x1337); s.low.write(0x1337); s.high.write(0x6969); std.testing.expect(s.val == 0x69691337); }