Expand description

Bit-Field Memory Slots

This module implements a load/store protocol for BitSlice regions that enables them to act as if they were a storage slot for integers. Implementations of the BitField trait provide behavior similar to C and C++ language bit-fields. While any BitSlice<T, O> instantiation is able to provide this behavior, the lack of specialization in the language means that it is instead only implemented for BitSlice<T, Lsb0> and BitSlice<T, Msb0> in order to gain a performance advantage.

Batched Behavior

Bit-field behavior can be simulated using BitSlice’s existing APIs; however, the inherent methods are all required to operate on each bit individually in sequence. In addition to the semantic load/store behavior this module describes, it also implements it in a way that takes advantage of the contiguity properties of the Lsb0 and Msb0 orderings in order to maximize how many bits are transferred in each cycle of the overall operation.

This is most efficient when using BitSlice<usize, O> as the storage bit-slice, or using .load::<usize>() or .store::<usize>() as the transfer type.

Bit-Slice Storage and Integer Value Relationships

BitField permits any type of integer, including signed integers, to be stored into or loaded out of a BitSlice<T, _> with any storage type T. While the examples in this module will largely use u8, just to keep the text concise, BitField is tested, and will work correctly, for any combination of types.

BitField implementations use the processor’s own concept of integer registers to operate. As such, the byte-wise memory access patters for types wider than u8 depends on your processor’s byte endianness, as well as which BitField method, and which BitOrder type parameter, you are using.

BitField only operates within processor registers; traffic of T elements between the memory bank and the processor register is controlled entirely by the processor.

If you do not want to introduce the processor’s byte endianness as a variable that affects the in-memory representation of stored integers, use BitSlice<u8, _> as the bit-field storage type. In particular, BitSlice<u8, Msb0> will fill memory in a way that intuitively matches what most debuggers show when inspecting memory.

On the other hand, if you do not care about memory representation and just need fast storage of less than an entire integer, BitSlice<Lsb0, usize> is likely your best bet. As always, the choice of type parameters is a trade-off with different advantages for each combination, which is why bitvec refuses to make the choice for you.

Signed Behavior

The length of the BitSlice that stores a value is considered to be the width of that value when it is loaded back out. As such, storing an i16 into a bit-slice of length 12 means that the stored value has type i12.

When calling .load::<i16>() on a 12-bit slice, the load will detect the sign bit of the i12 value and sign-extend it to i16. This means that storing 2048i16 into a 12-bit slice and then loading it back out into an i16 will produce -2048i16 (negative), not 2048i16 (positive), because 1 << 11 is the sign bit.

BitField does not record the true sign bit of an integer being stored, and will not attempt to set the sign bit of the narrowed value in storage. Storing -127i8 (0b1000_0001) into a 7-bit slice will load 1i8.

Register Bit Order Preservation

The implementations in this module assume that the bits within a value being transferred into or out of a bit-slice should not be re-ordered. While the implementations will segment a value in order to make it fit into bit-slice storage, and will order those segments in memory according to their type parameter and specific trait method called, each segment will remain individually unmodified.

If we consider the value 0b100_1011, segmented at the underscore, then the segments 0b100 and 0b1011 will be present somewhere in the bit-slice that stores them. They may be shifted within an element or re-ordered across elements, but each segment will not be changed.

Endianness

bitvec uses the BitOrder trait to describe the order of bits within a single memory element. This ordering is independent of, and does not consider, the ordering of memory elements in a sequence; bitvec is always “little-endian” in this regard: lower indices are in lower memory addresses, higher indices are in higher memory addresses.

However, BitField is explicitly aware of multiple storage elements in sequence. It is by design able to allow combinations such as <BitSlice<u8, Lsb0> as BitField>::store_be::<u32>. Even where the storage and value types are the same, or the value is narrower, the bit-slice may be spread across multiple elements and must segment the value across them.

The _be and _le orderings on BitField method names refer to the numeric significance of bit-slice storage elements.

In _be methods, lower-address storage elements will hold more-significant segments of the value, and higher-address storage will hold less-significant.

In _le methods, lower-address storage elements will hold less-significant segments of the value, and higher-address storage will hold more-significant.

Consider again the value 0b100_1011, segmented at the underscore. When used with .store_be(), it will be placed into memory as [0b…100…, 0b…1011…]; when used with .store_le(), it will be placed into memory as [0b…1011…, 0b…100…].

Bit-Ordering Behaviors

The _be and _le suffices select the ordering of storage elements in memory. The other critical aspect of the BitField memory behavior is selecting which bits in a storage element are used when a bit-slice has partial elements.

When BitSlice<_, Lsb0> produces a Domain::Region, its head is in the most-significant bits of its element and its tail is in the least-significant bits. When BitSlice<_, Msb0> produces a Region, its head is in the least-significant bits, and its tail is in the most-significant bits.

You can therefore use these combinations of BitOrder type parameter and BitField method suffix to select exactly the memory behavior you want for a storage region.

Each implementation of BitField has documentation showing exactly what its memory layout looks like, with code examples and visual inspections of memory. This documentation is likely collapsed by default when viewing the trait docs; be sure to use the [+] button to expand it!

Traits

C-Style Bit-Field Access