-
Notifications
You must be signed in to change notification settings - Fork 11
Description
Bitfields are a useful feature for low-level tasks like OS development in C. However, Go's language specification does not include native support for bitfields.
C Example:
This C structure uses bitfields to pack data efficiently:
struct vector_desc_t {
int flags: 16; // Bitfield: OR of VECDESC_FL_* defines (16 bits)
unsigned int cpu: 1; // Bitfield (1 bit)
unsigned int intno: 5; // Bitfield (5 bits)
int source: 8; // Bitfield: Interrupt mux flags (used when not shared, 8 bits)
shared_vector_desc_t *shared_vec_info; // Pointer (used when VECDESC_FL_SHARED)
vector_desc_t *next; // Pointer
};The numbers (16, 1, 5, 8) specify the exact number of bits each field occupies in memory.
Emulating Bitfields in Go
While Go lacks direct language-level support for bitfields, we can achieve similar functionality through code generation. This approach manually implements the bit manipulation operations.
The Go team exemplifies this technique in the https://cs.opensource.google/go/x/text/+/refs/tags/v0.28.0:internal/gen/bitfield/bitfield.go. For instance, code like this:
type myUint8 uint8
type test1 struct { // Represents 28 bits of data
foo uint16 `bitfield:",fob"` // Tag hints for field "foo" with getter "fob"
Bar int8 `bitfield:"5,baz"` // Tag: occupies 5 bits, getter named "baz"
Foo uint64 // Regular field (whole uint64)
bar myUint8 `bitfield:"3"` // Tag: occupies 3 bits (default getter "bar")
Bool bool `bitfield:""` // Tag: occupies 1 bit (default getter "Bool")
Baz int8 `bitfield:"3"` // Tag: occupies 3 bits (default getter "Baz")
}Can be processed by a generator to produce methods that manipulate the bits within a single underlying integer type (here, uint32 for the bitfields):
type test1 uint32 // Underlying type holding the packed bits
func (t test1) fob() uint16 { // Getter for field 'foo' (tag defined name "fob")
return uint16((t >> 16) & 0xffff) // Shift and mask for bits 16-31
}
func (t test1) baz() int8 { // Getter for field 'Bar' (tag defined name "baz")
return int8((t >> 11) & 0x1f) // Shift and mask for 5 bits at pos 11
}
func (t test1) bar() myUint8 { // Getter for field 'bar' (type myUint8)
return myUint8((t >> 8) & 0x7) // Shift and mask for 3 bits at pos 8
}
func (t test1) Bool() bool { // Getter for field 'Bool' (1 bit)
const bit = 1 << 7 // Bit mask for position 7
return t&bit == bit // Check if bit at pos 7 is set
}
func (t test1) Baz() int8 { // Getter for field 'Baz'
return int8((t >> 4) & 0x7) // Shift and mask for 3 bits at pos 4
}Memory alignment
By default, the most of compilers respect that memory alignment:
Size = ceil(bits ÷ unit_bits)+ padding for larger members.
(Unit size = sizeof(unsigned int), typically 32 bits).