Expand description
Batched load/store access to bitfields.
This module provides load/store access to bitfield regions that emulates the
ordinary memory bus. This functionality enables any BitSlice
span to be used
as a memory region, and provides the basis of a library-level analogue to the
bitfield language feature found in C and C++. Additionally, orderings that have
contiguous positions can transfer more than one bit in an operation, allowing a
performance acceleration over sequential bit-by-bit traversal.
The BitField
trait is open for implementation. Rust’s implementation rules
currently disallow a crate to implement a foreign trait on a foreign type, even
when parameterized over a local type. If you need such a BitField
implementation with a new BitOrder
type, please file an issue.
Batched Behavior
The first purpose of BitField
is to provide access to BitSlice
regions
as if they were an ordinary memory location. However, this can be done through
the BitSlice
sequential API. The second purpose of this trait is to accelerate
such access by using the parallel memory bus to transfer more than one bit at a
time when the region permits it. As such, implementors should provide a transfer
behavior based on shift/mask operations wherever possible, for as wide a span in
a memory element as possible.
Register Bit Order Preservation
As a default assumption, each element of the underlying memory region used to
store part of a value should not reörder the bit-pattern of that value. While
the BitOrder
argument is used to determine which segments of the memory
register are live for the purposes of this transfer, it should not be used to
map each individual bit of the transferred value to a corresponding bit of the
storage element. As an example, the Lsb0
and Msb0
implementations both
store the value 12u8
in memory as a four-bit span with its two
more-significant bits set and its two less-significant bits cleared; the
difference is only in which bits of an element are used to store the span.
Endianness
The _le
and _be
methods of BitField
refer to the order in which
successive T
elements of a storage region are assigned numeric significance
during a transfer. Within any particular T
element, the ordering of its memory
is not governed by the BitField
trait.
The provided BitOrder
implementors Lsb0
and Msb0
use the local
machine’s byte ordering, and do not reörder bytes during transfer.
_le
Methods
When storing a value M
into a sequence of memory elements T
, store_le
breaks M
into chunks from the least significant edge. The least significant
chunk is placed in the lowest-addressed element T
, then the next more
significant chunk is placed in the successive address, until the most
significant chunk of the value M
is placed in the highest address of a
location T
.
When loading a value M
out of a sequence of memory elements T
, load_le
uses the same chunking behavior: the lowest-addressed T
contains the least
significant chunk of the returned M
, then each successive address contains a
more significant chunk, until the highest address contains the most significant.
The BitOrder
implementation governs where in each T
location a fragment
of M
is stored.
Let us store 8 bits into memory, over an element boundary, using both Lsb0
and Msb0
orderings:
use bitvec::prelude::*;
let val: u8 = 0b11010_011;
// STUVW XYZ
let mut store = [0u8; 2];
store.view_bits_mut::<Lsb0>()
[5 .. 13]
.store_le(val);
assert_eq!(
store,
[0b011_00000, 0b000_11010],
// XYZ STUVW
);
store = [0u8; 2];
store.view_bits_mut::<Msb0>()
[5 .. 13]
.store_le(val);
assert_eq!(
store,
[0b00000_011, 0b11010_000],
// XYZ STUVW
);
In both cases, the lower three bits of val
were placed into the element at the
lower memory address. The choice of Lsb0
vs Msb0
changed which three
bits in the element were considered to be indexed by 5 .. 8
, but store_le
always placed the least three bits of val
, in ordinary register order, into
element [0]
. Similarly, the higher five bits of val
were placed into element
[1]
; Lsb0
and Msb0
selected which five bits in the element were indexed
by 8 .. 13
, and the bits retained their register order.
_be
Methods
When storing a value M
into a sequence of memory elements T
, store_be
breaks M
into chunks from the most significant edge. The most significant
chunk is placed in the lowest-addressed element T
, then the next less
significant chunk is placed in the successive address, until the least
significant chunk of the value M
is placed in the highest address of a
location T
.
When loading a value M
out of a sequence of memory elements T
, load_be
uses the same chunking behavior: the lowest-addressed T
contains the most
significant chunk of the returned M
, then each successive address contains a
less significant chunk, until the highest address contains the least
significant.
The BitOrder
implementation governs where in each T
location a fragment
of M
is stored.
Let us store 8 bits into memory, over an element boundary, using both Lsb0
and Msb0
orderings:
use bitvec::prelude::*;
let val: u8 = 0b110_10011;
// STU VWXYZ
let mut store = [0u8; 2];
store.view_bits_mut::<Lsb0>()
[5 .. 13]
.store_be(val);
assert_eq!(
store,
[0b110_00000, 0b000_10011],
// STU VWXYZ
);
store = [0u8; 2];
store.view_bits_mut::<Msb0>()
[5 .. 13]
.store_be(val);
assert_eq!(
store,
[0b00000_110, 0b10011_000],
// STU VWXYZ
);
In both cases, the higher three bits of val
were placed into the element at
the lower memory address. The choice of Lsb0
vs Msb0
changed which
three bits in the element were considered to be indexed by 5 .. 8
, but
store_be
always placed the greatest three bits of val
, in ordinary
register order, into element [0]
. Similarly, the lower five bits of val
were placed into element [1]
; Lsb0
and Msb0
selected which five bits in
the element were indexed by 8 .. 13
, and the bits retained their register
order.
M
and T
Relationships
BitField
permits any type of (unsigned) integer M
to be stored into or
loaded from a bit-slice region with any storage type T
. While the examples
used u8
for both, for brevity of writing out values, BitField
will still
operate correctly for any other combination of types.
Bitfield
implementations use the processor’s own concept of integer registers
to operate. As such, the byte-wise memory access patterns for types wider than
u8
depends on your processor’s byte-endianness, as well as which BitField
method and which BitOrder
implementation 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, stick to
BitSlice<_, u8>
as the bit-field driver. BitSlice<Msb0, u8>
will fill memory
in a way that matches a debugger or other memory inspections.
!