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.

!

Traits

Performs C-style bitfield access through a BitSlice.