use alloc::format;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::MetadataError;
pub const MAX_LOGICAL_DIMS: usize = 8;
pub const MAX_FRAGMENTS: usize = 16;
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct Tiling(u32);
impl Tiling {
pub const UNTILED: Tiling = Tiling(0);
pub fn new(fragments: &[usize]) -> Result<Tiling, MetadataError> {
let rule = |holds: bool, name: &str| {
holds
.then_some(())
.ok_or_else(|| invalid(format!("tiling {fragments:?}: {name}")))
};
rule(
fragments.len() <= MAX_LOGICAL_DIMS,
"at most 8 logical dims",
)?;
rule(
fragments.iter().all(|f| (1..=MAX_FRAGMENTS).contains(f)),
"1 to 16 fragments a dim",
)?;
let field = |(dim, &count): (usize, &usize)| ((count - 1) as u32) << (4 * dim);
Ok(Tiling(fragments.iter().enumerate().map(field).sum()))
}
pub fn fragments(self, logical_rank: usize) -> SmallVec<[usize; MAX_LOGICAL_DIMS]> {
(0..logical_rank).map(|dim| self.levels(dim) + 1).collect()
}
pub fn logical_rank(self, rank: usize) -> Result<usize, MetadataError> {
let extra: usize = (0..MAX_LOGICAL_DIMS).map(|dim| self.levels(dim)).sum();
let logical = rank
.checked_sub(extra)
.filter(|&logical| Tiling::new(&self.fragments(logical)) == Ok(self))
.ok_or_else(|| invalid(format!("{self:?} does not fit a rank-{rank} buffer")))?;
Ok(logical)
}
pub fn is_tiled(self) -> bool {
self.0 != 0
}
fn levels(self, dim: usize) -> usize {
((self.0 >> (4 * dim)) & 0b1111) as usize
}
}
fn invalid(reason: alloc::string::String) -> MetadataError {
MetadataError::Invalid { reason }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fragments_round_trip() {
let fragments = [1, 2, 2];
let tiling = Tiling::new(&fragments).unwrap();
assert!(tiling.is_tiled());
assert_eq!(tiling.fragments(3).as_slice(), &fragments);
assert_eq!(Tiling::new(&[2, 2]).unwrap(), Tiling(0x11));
assert_eq!(
Tiling::new(&[16, 4]).unwrap().fragments(2).as_slice(),
&[16, 4]
);
assert_eq!(Tiling::new(&[1, 1, 1]).unwrap(), Tiling::UNTILED);
assert_eq!(Tiling::UNTILED.fragments(2).as_slice(), &[1, 1]);
}
#[test]
fn logical_rank_is_the_physical_rank_less_the_extra_fragments() {
let tiling = Tiling::new(&[1, 2, 2]).unwrap();
assert_eq!(tiling.logical_rank(5), Ok(3));
assert!(tiling.logical_rank(4).is_err());
assert!(tiling.logical_rank(2).is_err());
assert_eq!(Tiling::UNTILED.logical_rank(4), Ok(4));
}
#[test]
fn refuses_what_the_protocol_cannot_hold() {
assert!(Tiling::new(&[1; 9]).is_err());
assert!(Tiling::new(&[17]).is_err());
assert!(Tiling::new(&[0]).is_err());
}
}