use uor_matmul_core::{Bound, Shape, NARROW_CAP};
use crate::tier::TierId;
pub const ADDRESS_LABEL_BYTES: usize = 71;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Manifest<'a> {
pub tier: TierId,
pub bound: u128,
pub rows: usize,
pub cols: usize,
pub block: usize,
pub codebook_sha256: &'a str,
pub codes_sha256: &'a str,
pub spec: &'a str,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Addressing {
Nothing,
ARunOf {
elements: usize,
lane_run: Option<usize>,
},
}
impl Addressing {
pub const fn of(tier: TierId, block: usize, bound: u128) -> Self {
match tier {
TierId::Identity | TierId::Runs => Self::Nothing,
_ if block == 0 => Self::Nothing,
_ => Self::ARunOf {
elements: block,
lane_run: lane_run(block, bound),
},
}
}
pub const fn addresses_an_element(self) -> bool {
matches!(self, Self::ARunOf { .. })
}
pub const fn addresses_a_run(self) -> bool {
matches!(self, Self::ARunOf { elements, .. } if elements > 1)
}
}
const fn lane_run(block: usize, bound: u128) -> Option<usize> {
if bound == 0 || block == 0 {
return Some(usize::MAX);
}
let square = match bound.checked_mul(bound) {
Some(v) => v,
None => return None,
};
let per_code = match square.checked_mul(block as u128) {
Some(v) => v,
None => return None,
};
let run = NARROW_CAP / per_code;
if run == 0 {
return None;
}
if run > usize::MAX as u128 {
Some(usize::MAX)
} else {
Some(run as usize)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum KappaError {
BufferTooSmall {
needed: usize,
offered: usize,
},
MalformedDigest,
NotAddressable,
}
impl core::fmt::Display for KappaError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::BufferTooSmall { needed, offered } => {
write!(
f,
"the canonical manifest needs {needed} bytes, {offered} offered"
)
}
Self::MalformedDigest => write!(f, "a digest field is not sha256:<64hex>"),
Self::NotAddressable => write!(f, "the addressing transform rejected the manifest"),
}
}
}
struct Out<'a> {
buf: &'a mut [u8],
at: usize,
overflowed: bool,
}
impl Out<'_> {
fn push(&mut self, bytes: &[u8]) {
let end = self.at.saturating_add(bytes.len()); if end > self.buf.len() {
self.overflowed = true;
self.at = end;
return;
}
self.buf[self.at..end].copy_from_slice(bytes);
self.at = end;
}
fn push_u128(&mut self, mut v: u128) {
let mut digits = [0u8; 39];
let mut n = 0;
if v == 0 {
self.push(b"0");
return;
}
while v > 0 {
digits[n] = b'0' + (v % 10) as u8;
v /= 10;
n += 1;
}
for i in (0..n).rev() {
self.push(&digits[i..=i]);
}
}
}
impl Manifest<'_> {
pub const fn addressing(&self) -> Addressing {
Addressing::of(self.tier, self.block, self.bound)
}
pub const fn reduces_along_the_block(&self, shape: Shape) -> bool {
self.rows == shape.n && self.cols == shape.k
}
pub const fn reduces_across_the_block(&self, shape: Shape) -> bool {
self.rows == shape.k && self.cols == shape.n
}
pub fn write_canonical_json(&self, out: &mut [u8]) -> Result<usize, KappaError> {
for d in [self.codebook_sha256, self.codes_sha256] {
if !is_sha256(d) {
return Err(KappaError::MalformedDigest);
}
}
let mut w = Out {
buf: out,
at: 0,
overflowed: false,
};
w.push(b"{\"block\":");
w.push_u128(self.block as u128);
w.push(b",\"bound\":");
w.push_u128(self.bound);
w.push(b",\"codebook_sha256\":\"");
w.push(self.codebook_sha256.as_bytes());
w.push(b"\",\"codes_sha256\":\"");
w.push(self.codes_sha256.as_bytes());
w.push(b"\",\"cols\":");
w.push_u128(self.cols as u128);
w.push(b",\"rows\":");
w.push_u128(self.rows as u128);
w.push(b",\"spec\":\"");
w.push(self.spec.as_bytes());
w.push(b"\",\"tier\":\"");
w.push(self.tier.as_str().as_bytes());
w.push(b"\"}");
if w.overflowed {
return Err(KappaError::BufferTooSmall {
needed: w.at,
offered: w.buf.len(),
});
}
Ok(w.at)
}
pub fn of<E, Bd, C>(
matrix: &crate::CodedMatrix<'_, E, Bd, C>,
codebook_sha256: &'static str,
codes_sha256: &'static str,
spec: &'static str,
) -> Manifest<'static>
where
E: uor_matmul_core::Element,
Bd: Bound,
C: crate::Codec<E, Bd>,
{
Manifest {
tier: C::TIER,
bound: Bd::VALUE,
rows: matrix.rows(),
cols: matrix.cols(),
block: C::MAX_BLOCK,
codebook_sha256,
codes_sha256,
spec,
}
}
}
fn is_sha256(s: &str) -> bool {
let Some(hex) = s.strip_prefix("sha256:") else {
return false;
};
hex.len() == 64
&& hex
.bytes()
.all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())
}
#[cfg(feature = "kappa")]
pub fn address_into(
manifest: &Manifest<'_>,
scratch: &mut [u8],
out: &mut [u8; ADDRESS_LABEL_BYTES],
) -> Result<(), KappaError> {
let n = manifest.write_canonical_json(scratch)?;
let outcome = uor_addr_1::address(&scratch[..n]).map_err(|_| KappaError::NotAddressable)?;
let label = outcome.address.as_bytes();
if label.len() != ADDRESS_LABEL_BYTES {
return Err(KappaError::NotAddressable);
}
out.copy_from_slice(label);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
const D0: &str = "sha256:0000000000000000000000000000000000000000000000000000000000000000";
const D1: &str = "sha256:1111111111111111111111111111111111111111111111111111111111111111";
const D2: &str = "sha256:2222222222222222222222222222222222222222222222222222222222222222";
#[test]
fn canonical_json_is_byte_stable_ck_08() {
let m = Manifest {
tier: TierId::Book,
bound: 127,
rows: 4096,
cols: 4096,
block: 8,
codebook_sha256: D0,
codes_sha256: D1,
spec: "uor-matmul/1",
};
let mut buf = [0u8; 512];
let n = m.write_canonical_json(&mut buf).unwrap();
let text = core::str::from_utf8(&buf[..n]).unwrap();
assert_eq!(
text,
concat!(
"{\"block\":8,\"bound\":127,",
"\"codebook_sha256\":\"sha256:00000000000000000000000000000000",
"00000000000000000000000000000000\",",
"\"codes_sha256\":\"sha256:11111111111111111111111111111111",
"11111111111111111111111111111111\",",
"\"cols\":4096,\"rows\":4096,\"spec\":\"uor-matmul/1\",\"tier\":\"Book\"}"
)
);
}
#[test]
fn a_short_buffer_reports_the_need_ck_08() {
let m = Manifest {
tier: TierId::Identity,
bound: 1,
rows: 1,
cols: 1,
block: 1,
codebook_sha256: D0,
codes_sha256: D1,
spec: "uor-matmul/1",
};
let mut buf = [0u8; 8];
match m.write_canonical_json(&mut buf) {
Err(KappaError::BufferTooSmall { needed, offered }) => {
assert!(needed > 8);
assert_eq!(offered, 8);
}
other => panic!("expected BufferTooSmall, got {other:?}"),
}
}
#[test]
fn a_malformed_digest_is_rejected_ck_08() {
let m = Manifest {
tier: TierId::Identity,
bound: 1,
rows: 1,
cols: 1,
block: 1,
codebook_sha256: "not-a-digest",
codes_sha256: D1,
spec: "uor-matmul/1",
};
let mut buf = [0u8; 512];
assert_eq!(
m.write_canonical_json(&mut buf),
Err(KappaError::MalformedDigest)
);
}
#[test]
fn arena_manifest_spelling_is_byte_stable_ck_08() {
let m = Manifest {
tier: TierId::Arena,
bound: u128::MAX,
rows: 4096,
cols: 4096,
block: 1,
codebook_sha256: D0,
codes_sha256: D1,
spec: "uor-matmul/1",
};
let mut buf = [0u8; 512];
let n = m.write_canonical_json(&mut buf).unwrap();
let text = core::str::from_utf8(&buf[..n]).unwrap();
assert_eq!(
text,
concat!(
"{\"block\":1,\"bound\":340282366920938463463374607431768211455,",
"\"codebook_sha256\":\"sha256:00000000000000000000000000000000",
"00000000000000000000000000000000\",",
"\"codes_sha256\":\"sha256:11111111111111111111111111111111",
"11111111111111111111111111111111\",",
"\"cols\":4096,\"rows\":4096,\"spec\":\"uor-matmul/1\",\"tier\":\"Arena\"}"
)
);
}
#[test]
fn addressing_is_read_from_the_declaration_cs_10() {
let e8 = Manifest {
tier: TierId::Book,
bound: 128,
rows: 4096,
cols: 4096,
block: 8,
codebook_sha256: D0,
codes_sha256: D1,
spec: "uor-matmul/1",
};
let other = Manifest {
codebook_sha256: D2,
codes_sha256: D2,
..e8
};
assert_ne!(e8, other, "different bytes are a different artifact");
let mut lhs = [0u8; 512];
let mut rhs = [0u8; 512];
let ln = e8.write_canonical_json(&mut lhs).unwrap();
let rn = other.write_canonical_json(&mut rhs).unwrap();
assert_ne!(lhs[..ln], rhs[..rn], "and a different canonical manifest");
assert_eq!(e8.addressing(), other.addressing());
let want_run = (NARROW_CAP / (8 * 128 * 128)).min(usize::MAX as u128) as usize;
assert_eq!(
e8.addressing(),
Addressing::ARunOf {
elements: 8,
lane_run: Some(want_run),
}
);
assert!(e8.addressing().addresses_a_run());
assert!(e8.addressing().addresses_an_element());
let scalar = Manifest { block: 1, ..e8 };
assert!(scalar.addressing().addresses_an_element());
assert!(!scalar.addressing().addresses_a_run());
assert_ne!(scalar.addressing(), e8.addressing());
assert_eq!(Addressing::of(TierId::Book, 0, 128), Addressing::Nothing);
assert_eq!(
Addressing::of(TierId::Arena, 1, u128::MAX),
Addressing::ARunOf {
elements: 1,
lane_run: None,
}
);
assert_eq!(
Addressing::of(TierId::Book, 8, 1u128 << 40),
Addressing::ARunOf {
elements: 8,
lane_run: None,
}
);
assert_eq!(
Addressing::of(TierId::Book, 8, 0),
Addressing::ARunOf {
elements: 8,
lane_run: Some(usize::MAX),
}
);
assert_eq!(
Addressing::of(TierId::Identity, 1, 128),
Addressing::Nothing
);
assert_eq!(Addressing::of(TierId::Runs, 8, 128), Addressing::Nothing);
let shape = Shape { m: 3, k: 64, n: 40 };
let along = Manifest {
rows: 40,
cols: 64,
..e8
};
let across = Manifest {
rows: 64,
cols: 40,
..e8
};
assert!(along.reduces_along_the_block(shape));
assert!(!along.reduces_across_the_block(shape));
assert!(across.reduces_across_the_block(shape));
assert!(!across.reduces_along_the_block(shape));
assert_eq!(along.addressing(), across.addressing());
let square = Shape { m: 3, k: 64, n: 64 };
let s = Manifest {
rows: 64,
cols: 64,
..e8
};
assert!(s.reduces_along_the_block(square));
assert!(s.reduces_across_the_block(square));
}
}