Skip to content

Support bitfields #542

@MeteorsLiu

Description

@MeteorsLiu

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions