Skip to content

Instantly share code, notes, and snippets.

@lemon32767
Last active August 25, 2020 21:56
Show Gist options
  • Save lemon32767/489da29d71329436cb17935dcddf6b38 to your computer and use it in GitHub Desktop.
Save lemon32767/489da29d71329436cb17935dcddf6b38 to your computer and use it in GitHub Desktop.
zig bitreg type
const std = @import("std");
const assert = std.debug.assert;
const trait = std.meta.trait;
//example:
// BitReg(u32, .{
// .{.first_4_bits, 0, 4},
// .{.next_7_bits, 4, 7},
// })
pub fn BitReg(comptime T: type, comptime fields: anytype) type {
const check_or = struct {
fn check_or(comptime ok: bool, comptime msg: []const u8) void {
if (!ok) @compileError(msg);
}
}.check_or;
check_or(trait.isUnsignedInt(T), "type must be unsigned integer type, was " ++ @typeName(T));
assert(trait.isIndexable(@TypeOf(fields)));
check_or(fields.len > 0, "empty fields parameter");
for (fields) |field, i| {
//check_or correct format
assert(field.len == 3);
check_or(trait.is(.EnumLiteral)(@TypeOf(field[0])), "field name must be @Type(.EnumLiteral)");
//check_or that it's not duplicate
var idx: usize = 0;
while (idx < i) : (idx += 1) {
check_or(fields[idx][0] != field[0], "field ." ++ @tagName(field[0]) ++ " is a duplicate");
}
//continue checking format
const Field1 = @TypeOf(field[1]);
const Field2 = @TypeOf(field[2]);
check_or((trait.is(.Int)(Field1) or trait.is(.ComptimeInt)(Field1)) and field[1] >= 0, "field offset must be positive/zero integer");
check_or((trait.is(.Int)(Field2) or trait.is(.ComptimeInt)(Field2)) and field[2] > 0, "field width must be positive integer");
check_or(field[1] + field[2] <= @bitSizeOf(T), "field ." ++ @tagName(field[0]) ++ " doesn't fit in " ++ @typeName(T));
}
//for comptime error reporting
comptime var field_list_str: []const u8 = "";
for (fields) |field, i| {
field_list_str = field_list_str ++ "." ++ @tagName(field[0]);
if (i < fields.len - 2) {
field_list_str = field_list_str ++ ", ";
} else if (i < fields.len - 1) {
field_list_str = field_list_str ++ " or ";
}
}
const bestFitInt = struct {
fn bestFitInt(comptime reg: @Type(.EnumLiteral)) type {
for (fields) |field| {
if (field[0] == reg) {
return @Type(.{ .Int = .{ .is_signed = false, .bits = field[2] } });
}
}
@compileError("invalid field: ." ++ @tagName(reg) ++ "; should be one of: " ++ field_list_str);
unreachable;
}
}.bestFitInt;
return extern struct {
raw: T,
//get value of field 'reg', as an unsigned integer of bit size equal to field bit width
//compile error if field isn't defined
pub inline fn get(this: @This(), comptime reg: @Type(.EnumLiteral)) bestFitInt(reg) {
inline for (fields) |field| {
if (field[0] == reg) {
const offset: T = field[1];
const width: T = field[2];
return @truncate(bestFitInt(reg), this.raw >> offset);
}
}
comptime unreachable;
}
//get value of field 'reg', as an unsigned integer of the backing integer type
//compile error if field isn't defined
pub inline fn getCast(this: @This(), comptime reg: @Type(.EnumLiteral)) T {
return @intCast(T, this.get(reg));
}
//set value of field 'reg', from an unsigned integer of bit size equal to field bit width
//compile error if field isn't defined
pub inline fn set(this: *@This(), comptime reg: @Type(.EnumLiteral), value: bestFitInt(reg)) void {
inline for (fields) |field| {
if (field[0] == reg) {
const offset: T = field[1];
const width: T = field[2];
const mask: T = ((1 << width) - 1) << offset;
this.raw = (this.raw & ~mask) | ((@as(T, value) << offset) & mask);
return;
}
}
comptime unreachable;
}
//set value of field 'reg', from an arbitrary integer value safely casted to an unsigned integer of bit size equal to field bit width
//compile error if field isn't defined
pub inline fn setCast(this: *@This(), comptime reg: @Type(.EnumLiteral), value: anytype) void {
return this.set(reg, @intCast(bestFitInt(reg), value));
}
//set value of field 'reg', from an arbitrary integer value truncated to an unsigned integer of bit size equal to field bit width
//compile error if field isn't defined
pub inline fn setTrunc(this: *@This(), comptime reg: @Type(.EnumLiteral), value: anytype) void {
return this.set(reg, @truncate(bestFitInt(reg), value));
}
};
}
const testing = std.testing;
test "bitreg" {
const Testt = BitReg(u32, .{
.{ .a, 0, 4 },
.{ .b, 4, 8 },
.{ .c, 14, 2 },
//.{ .toobig, 30, 3 }, //this will error because offset(30)+width(3) doesn't fit in u32
//.{ .zerosize, 0, 0 },
//.{ .negative, -1, 1 },
//.{ .negative2, 0, -1 },
// .{ .a, 0, 1 }, //duplicate
});
testing.expectEqual(@sizeOf(Testt), @sizeOf(u32));
var foo: Testt = .{ .raw = 0b1110001_0110 };
testing.expectEqual(@TypeOf(foo.get(.a)), u4);
testing.expectEqual(@TypeOf(foo.get(.b)), u8);
testing.expectEqual(@TypeOf(foo.get(.c)), u2);
testing.expectEqual(foo.get(.a), 0b0110);
testing.expectEqual(foo.get(.b), 0b1110001);
testing.expectEqual(foo.get(.c), 0);
foo.set(.a, 0b1001);
testing.expectEqual(foo.get(.a), 0b1001);
testing.expectEqual(foo.get(.b), 0b1110001);
testing.expectEqual(foo.get(.c), 0);
foo.set(.b, 1);
testing.expectEqual(foo.get(.a), 0b1001);
testing.expectEqual(foo.get(.b), 0b0000001);
testing.expectEqual(foo.raw, 0b0000001_1001);
foo.set(.c, 2);
testing.expectEqual(foo.raw, 0b10_00_00000001_1001);
// foo.set(.c, 4); // compile error: 4 cannot be coerced into u2
var x = @src().line * 100; //force runtime evaluation
//foo.set(.a, @intCast(u4, x)); //panic: integer cast truncated bits
foo.setTrunc(.a, x); //works
//foo.setCast(.a, x); //panic: integer cast truncated bits
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment