134 lines
6.6 KiB
Markdown
134 lines
6.6 KiB
Markdown
# 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!
|
||
|
||
[`BitField`]: self::BitField
|
||
[`BitOrder`]: crate::order::BitOrder
|
||
[`BitSlice`]: crate::slice::BitSlice
|
||
[`Domain::Region`]: crate::domain::Domain::Region
|