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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions