#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Run {
pub start: u32,
pub width: u32,
pub align: u32,
}
impl Run {
pub(crate) fn at(base: u32, offset: u64, start: u32, width: u32) -> Run {
let align = if offset == 0 {
base
} else {
base.min(1 << offset.trailing_zeros().min(16))
};
Run { start, width, align: align.max(1) }
}
pub(crate) fn bytes(self) -> u32 {
(self.start + self.width).div_ceil(8)
}
pub(crate) fn unit(self) -> u32 {
(self.bytes() * 8).next_power_of_two()
}
pub(crate) fn accessible(self) -> bool {
self.width > 0 && self.bytes() <= 8
}
pub(crate) fn pieces(self) -> Vec<Piece> {
let mut pieces = Vec::new();
let (mut at, mut left) = (0, self.bytes());
let end = self.start + self.width;
while left > 0 {
let size = if left.is_power_of_two() { left } else { left.next_power_of_two() / 2 };
pieces.push(Piece {
offset: u64::from(at),
size,
align: self.align.min(size),
from: self.start.max(at * 8),
to: end.min((at + size) * 8),
});
at += size;
left -= size;
}
pieces
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Piece {
pub offset: u64,
pub size: u32,
pub align: u32,
pub from: u32,
pub to: u32,
}
impl Piece {
pub(crate) fn mask(self) -> u128 {
let width = self.to - self.from;
let ones = if width >= 128 { u128::MAX } else { (1u128 << width) - 1 };
ones << (self.from - self.offset as u32 * 8)
}
pub(crate) fn whole(self) -> bool {
let base = self.offset as u32 * 8;
self.from == base && self.to == base + self.size * 8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_run_inside_one_byte_is_one_byte_wide_access() {
let run = Run { start: 3, width: 4, align: 4 };
assert_eq!(run.bytes(), 1);
assert_eq!(run.unit(), 8);
let pieces = run.pieces();
assert_eq!(pieces.len(), 1);
assert_eq!(pieces[0], Piece { offset: 0, size: 1, align: 1, from: 3, to: 7 });
assert_eq!(pieces[0].mask(), 0b0111_1000);
assert!(!pieces[0].whole());
}
#[test]
fn a_run_that_fills_its_bytes_needs_no_load_before_a_store() {
let run = Run { start: 0, width: 32, align: 4 };
let pieces = run.pieces();
assert_eq!(pieces.len(), 1);
assert_eq!(pieces[0], Piece { offset: 0, size: 4, align: 4, from: 0, to: 32 });
assert!(pieces[0].whole());
assert_eq!(pieces[0].mask(), 0xffff_ffff);
}
#[test]
fn a_run_over_three_bytes_is_two_accesses_and_neither_reaches_the_fourth() {
let run = Run { start: 0, width: 24, align: 4 };
assert_eq!(run.bytes(), 3);
assert_eq!(run.unit(), 32);
let pieces = run.pieces();
assert_eq!(pieces.len(), 2);
assert_eq!(pieces[0], Piece { offset: 0, size: 2, align: 2, from: 0, to: 16 });
assert_eq!(pieces[1], Piece { offset: 2, size: 1, align: 1, from: 16, to: 24 });
assert!(pieces.iter().all(|piece| piece.whole()));
}
#[test]
fn a_packed_run_is_covered_by_pieces_that_start_where_they_can_be_addressed() {
let run = Run { start: 1, width: 32, align: 1 };
assert_eq!(run.bytes(), 5);
assert_eq!(run.unit(), 64);
let pieces = run.pieces();
assert_eq!(pieces.len(), 2);
assert_eq!(pieces[0], Piece { offset: 0, size: 4, align: 1, from: 1, to: 32 });
assert_eq!(pieces[1], Piece { offset: 4, size: 1, align: 1, from: 32, to: 33 });
assert_eq!(pieces[1].mask(), 0b1);
assert!(!pieces[1].whole());
}
#[test]
fn the_pieces_cover_every_bit_of_the_run_and_nothing_outside_its_bytes() {
for start in 0..8 {
for width in 1..=57 {
let run = Run { start, width, align: 8 };
if !run.accessible() {
continue;
}
let pieces = run.pieces();
let covered: u32 = pieces.iter().map(|piece| piece.to - piece.from).sum();
assert_eq!(covered, width, "{run:?}");
let bytes: u32 = pieces.iter().map(|piece| piece.size).sum();
assert_eq!(bytes, run.bytes(), "{run:?}");
for piece in pieces {
assert!(piece.size.is_power_of_two(), "{piece:?}");
assert_eq!(piece.offset % u64::from(piece.size), 0, "{piece:?}");
assert!(piece.from < piece.to, "{piece:?}");
}
}
}
}
#[test]
fn a_run_wider_than_the_widest_access_is_not_one_this_builds() {
assert!(Run { start: 0, width: 64, align: 8 }.accessible());
assert!(!Run { start: 1, width: 64, align: 1 }.accessible());
assert!(!Run { start: 0, width: 0, align: 4 }.accessible());
}
#[test]
fn an_offset_leaves_a_base_alignment_with_what_it_divides_by() {
assert_eq!(Run::at(4, 0, 0, 3).align, 4);
assert_eq!(Run::at(4, 2, 0, 3).align, 2);
assert_eq!(Run::at(4, 3, 0, 3).align, 1);
assert_eq!(Run::at(16, 8, 0, 3).align, 8);
assert_eq!(Run::at(1, 8, 0, 3).align, 1);
}
}