uor_matmul_codec/kappa.rs
1//! Content addressing (ยง6.4).
2//!
3//! A weight artifact's identity is the kappa label of its **canonical
4//! manifest**, not of its code bytes, so that a transcode between tiers is
5//! visibly a different artifact with a provably identical decoded stream. That
6//! is `CL-MM01` with an address attached to each side, and `CK-05` is the test
7//! that says it.
8//!
9//! Bulk arrays are referenced by digest rather than inlined, so the manifest
10//! stays inside `uor-addr-1`'s depth and width ceilings and the address stays
11//! cheap.
12//!
13//! # Allocation
14//!
15//! [`Manifest::write_canonical_json`] writes into a caller-supplied buffer and
16//! allocates nothing, so the default build of this crate remains heap-free
17//! (R7). The `kappa` feature additionally pulls in `uor-addr-1` to turn that
18//! JSON into a label; that crate owns its own allocation, which is why the
19//! feature is off by default and why the manifest writer is usable without it.
20
21use uor_matmul_core::{Bound, Shape, NARROW_CAP};
22
23use crate::tier::TierId;
24
25/// Bytes in a kappa label.
26pub const ADDRESS_LABEL_BYTES: usize = 71;
27
28/// A canonical weight-artifact manifest.
29///
30/// The field set and its JSON spelling are normative and are restated in
31/// `ARCHITECTURE.md`. Changing either changes every artifact's identity, which
32/// is why the schema carries a `spec` tag.
33///
34/// There is deliberately no code-width field. The width is a property of the
35/// code *bytes*, which `codes_sha256` already distinguishes: a `u8` spelling
36/// and a `u16` spelling of one tier decode alike and digest differently, so
37/// they are two artifacts with two addresses --- the same rule `CK-05` states
38/// for equal-decoding codecs generally. Nothing downstream of the manifest
39/// reads the codes width-sensitively: the only reader is the decoder, which
40/// learns the width from the artifact's own type.
41#[derive(Clone, Copy, PartialEq, Eq, Debug)]
42pub struct Manifest<'a> {
43 /// Which tier decodes the codes.
44 pub tier: TierId,
45 /// The alphabet bound the decoded stream satisfies.
46 pub bound: u128,
47 /// Rows of the decoded matrix.
48 pub rows: usize,
49 /// Decoded elements per row.
50 pub cols: usize,
51 /// Alphabet elements produced per code.
52 pub block: usize,
53 /// `sha256:<64hex>` of the codebook, or of the empty table for a codec that
54 /// has none.
55 pub codebook_sha256: &'a str,
56 /// `sha256:<64hex>` of the code bytes.
57 pub codes_sha256: &'a str,
58 /// The schema tag.
59 pub spec: &'a str,
60}
61
62/// What a manifest says about addressing the artifact it describes.
63///
64/// Derived, never declared. [`Manifest`]'s field set is normative and carries
65/// no addressing field; it gains none here, because a field would be a second
66/// source for what the tier, the block and the bound already fix (R10) as well
67/// as a change to every artifact's identity.
68///
69/// Those three are the manifest's fields that describe the *code*. The two that
70/// describe the artifact's *bytes* --- `codes_sha256` and `codebook_sha256` ---
71/// are not read, and that absence is the whole of `CS-10`: two artifacts of one
72/// tier, one block and one bound address alike however far apart their code
73/// bytes are, so a traversal chosen from this cannot have probed either one.
74///
75/// It says nothing about whether a *table* over the code space exists. That is
76/// [`crate::Enumerable`]'s question, it is answered by the type at the
77/// tabulated traversal's boundary rather than by a token, and a composing tier
78/// --- [`crate::Packed`], [`crate::Offset`], [`crate::Transcode`] --- reports
79/// its own tier while inheriting its inner codec's enumeration. What is stated
80/// here is the block: how many elements one code names, and how far a lane
81/// carries their partial sums.
82#[derive(Clone, Copy, PartialEq, Eq, Debug)]
83pub enum Addressing {
84 /// No code of this artifact indexes a decode.
85 ///
86 /// Either the manifest names no element per code at all, or the tier is one
87 /// of the two with nothing between a code and an element:
88 /// [`TierId::Identity`], whose codes *are* the alphabet, so its code space
89 /// is as wide as the element type; and [`TierId::Runs`], whose code widths
90 /// are the data, so there is no `p`-th block for anything to be built per.
91 /// Those are the two tiers `ARCHITECTURE.md` names as admitting no table;
92 /// neither implements [`crate::Enumerable`], and no composition of them can,
93 /// because a composing tier reports its own token and its own enumeration.
94 ///
95 /// Reading the tier for this is not a dispatch on the *answer*: two codecs
96 /// with different tiers and equal decodes still write byte-identical output
97 /// (`CK-05`), and the table and the stream are held to the same bytes
98 /// either way (`CD-13`). What it decides is which factorizations exist.
99 Nothing,
100 /// A code names `elements` consecutive elements of one row, so a table
101 /// indexed by the code space can carry their partial sum against an
102 /// activation block of the same length.
103 ARunOf {
104 /// Consecutive elements of a row one code names.
105 ///
106 /// One is well formed and is what every scalar tier declares. It is the
107 /// block over which a table sums nothing --- one code, one product ---
108 /// which is why `tabulation_pays` refuses it on op count and routes the
109 /// arena tier back to the dense traversal.
110 elements: usize,
111 /// Partial sums of one such run that a narrow lane word holds exactly.
112 ///
113 /// A product of two alphabet elements has magnitude at most `bound^2`,
114 /// and a run of `elements` of them at most `elements * bound^2`, so a
115 /// lane holding [`NARROW_CAP`] holds this many runs and no more.
116 ///
117 /// `None` when no run fits one at all: a bound wide enough that a single
118 /// block already exceeds the lane, and in particular the `u128::MAX` a
119 /// float codebook declares through `Whole`, which is not a magnitude.
120 /// The reduction is then carried in the exact accumulator --- where a
121 /// family with no narrow register was always going to carry it.
122 lane_run: Option<usize>,
123 },
124}
125
126impl Addressing {
127 /// The addressing a tier, a block and a bound declare. The whole of the
128 /// derivation, and its only entry point.
129 pub const fn of(tier: TierId, block: usize, bound: u128) -> Self {
130 match tier {
131 TierId::Identity | TierId::Runs => Self::Nothing,
132 // A code that names no element addresses nothing, whatever its tier.
133 // `CodedMatrix::new` refuses such a codec outright, so this is the
134 // same non-existence stated one step earlier.
135 _ if block == 0 => Self::Nothing,
136 _ => Self::ARunOf {
137 elements: block,
138 lane_run: lane_run(block, bound),
139 },
140 }
141 }
142
143 /// Does one code name an element at all?
144 pub const fn addresses_an_element(self) -> bool {
145 matches!(self, Self::ARunOf { .. })
146 }
147
148 /// Does one code name a *run*, so that a table entry is a partial sum of
149 /// more than one product?
150 ///
151 /// This is the term the tabulated traversal's break-even turns on, and it
152 /// is false at `MAX_BLOCK == 1` for the reason stated on `elements` above:
153 /// a table of one product per entry repays no build at any width.
154 pub const fn addresses_a_run(self) -> bool {
155 matches!(self, Self::ARunOf { elements, .. } if elements > 1)
156 }
157}
158
159/// Partial sums of a `block`-long run that one narrow lane word holds exactly.
160///
161/// The same derivation `uor_matmul_core`'s narrow run is, one level up: there it
162/// is products that are counted against [`NARROW_CAP`] and here it is blocks of
163/// them, so the per-code magnitude carries an extra factor of `block` and
164/// nothing else changes. The cap is read from core rather than restated,
165/// because a constant with two sources is a constant with none (R10).
166const fn lane_run(block: usize, bound: u128) -> Option<usize> {
167 if bound == 0 || block == 0 {
168 // An alphabet of one value, or a code that names no element: neither
169 // moves the lane, so no run of them ever fills it. The same answer core's
170 // own run derivation gives at a bound of zero, for the same reason.
171 return Some(usize::MAX);
172 }
173 let square = match bound.checked_mul(bound) {
174 Some(v) => v,
175 // A bound that cannot be squared is not a magnitude: it is `Whole`'s,
176 // which declares that the codebook itself is the alphabet.
177 None => return None,
178 };
179 let per_code = match square.checked_mul(block as u128) {
180 Some(v) => v,
181 None => return None,
182 };
183 let run = NARROW_CAP / per_code;
184 if run == 0 {
185 return None;
186 }
187 // A run wider than the machine can index is the machine's limit, not the
188 // lane's, and clamping says so without inventing a smaller one (R8).
189 if run > usize::MAX as u128 {
190 Some(usize::MAX)
191 } else {
192 Some(run as usize)
193 }
194}
195
196/// The manifest could not be rendered or addressed.
197///
198/// Neither variant can be caused by the *values* in a matrix, only by a
199/// manifest that does not describe an artifact. Like [`uor_matmul_core::
200/// NotAProduct`], this is non-existence, decided before any arithmetic.
201#[derive(Clone, Copy, PartialEq, Eq, Debug)]
202#[non_exhaustive]
203pub enum KappaError {
204 /// The caller's buffer is too small for the canonical JSON.
205 BufferTooSmall {
206 /// Bytes the manifest needs.
207 needed: usize,
208 /// Bytes the caller offered.
209 offered: usize,
210 },
211 /// A digest field is not a `sha256:<64hex>` string.
212 MalformedDigest,
213 /// The addressing transform rejected the manifest.
214 NotAddressable,
215}
216
217impl core::fmt::Display for KappaError {
218 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219 match self {
220 Self::BufferTooSmall { needed, offered } => {
221 write!(
222 f,
223 "the canonical manifest needs {needed} bytes, {offered} offered"
224 )
225 }
226 Self::MalformedDigest => write!(f, "a digest field is not sha256:<64hex>"),
227 Self::NotAddressable => write!(f, "the addressing transform rejected the manifest"),
228 }
229 }
230}
231
232/// A cursor that writes into a caller's buffer and never allocates.
233struct Out<'a> {
234 buf: &'a mut [u8],
235 at: usize,
236 overflowed: bool,
237}
238
239impl Out<'_> {
240 fn push(&mut self, bytes: &[u8]) {
241 let end = self.at.saturating_add(bytes.len()); // R3-ok: a buffer cursor, checked below
242 if end > self.buf.len() {
243 self.overflowed = true;
244 self.at = end;
245 return;
246 }
247 self.buf[self.at..end].copy_from_slice(bytes);
248 self.at = end;
249 }
250
251 fn push_u128(&mut self, mut v: u128) {
252 // 39 is the decimal width of `u128::MAX`, so this buffer cannot be
253 // outrun by any input. A derivation, not a choice (R8).
254 let mut digits = [0u8; 39];
255 let mut n = 0;
256 if v == 0 {
257 self.push(b"0");
258 return;
259 }
260 while v > 0 {
261 digits[n] = b'0' + (v % 10) as u8;
262 v /= 10;
263 n += 1;
264 }
265 for i in (0..n).rev() {
266 self.push(&digits[i..=i]);
267 }
268 }
269}
270
271impl Manifest<'_> {
272 /// What this manifest says about addressing the artifact it describes.
273 ///
274 /// Read from the tier, the block and the bound, and from neither digest.
275 /// See [`Addressing`] for why that absence is the claim rather than an
276 /// omission (`CS-10`).
277 pub const fn addressing(&self) -> Addressing {
278 Addressing::of(self.tier, self.block, self.bound)
279 }
280
281 /// Does this artifact stand as the coded operand of `shape` with the
282 /// reduction running *along* its code blocks?
283 ///
284 /// `rows == n` and `cols == k`: one coded row per output column, so a code
285 /// block is a run of the reduction, a table entry is a partial sum of it,
286 /// and the product is `C := A * W^T`. That is the orientation
287 /// `uor_matmul_gemm::TabulatedTriple` takes, and this asks its constructor's
288 /// question of the *declaration*, before there is anything to construct.
289 ///
290 /// Two queries rather than one enum, because at `k == n` a square coded
291 /// operand satisfies both and an enum would have to pick --- inventing a
292 /// distinction the declaration does not make. Which of the two products is
293 /// meant is then named by the triple the caller builds, and neither answer
294 /// is wrong.
295 pub const fn reduces_along_the_block(&self, shape: Shape) -> bool {
296 self.rows == shape.n && self.cols == shape.k
297 }
298
299 /// Does this artifact stand as the coded operand of `shape` with the
300 /// reduction running *across* its code blocks?
301 ///
302 /// `rows == k` and `cols == n`: one coded row per step of the reduction, so
303 /// a code block is a run of `MAX_BLOCK` different *output columns* and there
304 /// is nothing for a partial sum to be a partial sum of. That is the
305 /// orientation `uor_matmul_gemm::CodedTriple` takes --- the streaming one,
306 /// which needs no offer at all. Not a lesser orientation and not a fallback:
307 /// it is the one a `k x n` quantized weight is already stored in.
308 pub const fn reduces_across_the_block(&self, shape: Shape) -> bool {
309 self.rows == shape.k && self.cols == shape.n
310 }
311
312 /// Write the JCS-RFC8785 canonical JSON for this manifest.
313 ///
314 /// Returns the number of bytes written. Object members are emitted in
315 /// lexicographic order of their keys, with no whitespace and no escapes,
316 /// which is what JCS requires and what makes two independently produced
317 /// manifests of the same artifact byte-identical.
318 ///
319 /// Allocates nothing. If `out` is too small the needed length is reported
320 /// rather than a partial write being passed off as a manifest.
321 pub fn write_canonical_json(&self, out: &mut [u8]) -> Result<usize, KappaError> {
322 for d in [self.codebook_sha256, self.codes_sha256] {
323 if !is_sha256(d) {
324 return Err(KappaError::MalformedDigest);
325 }
326 }
327 let mut w = Out {
328 buf: out,
329 at: 0,
330 overflowed: false,
331 };
332
333 // Lexicographic key order: block, bound, codebook_sha256,
334 // codes_sha256, cols, rows, spec, tier.
335 w.push(b"{\"block\":");
336 w.push_u128(self.block as u128);
337 w.push(b",\"bound\":");
338 w.push_u128(self.bound);
339 w.push(b",\"codebook_sha256\":\"");
340 w.push(self.codebook_sha256.as_bytes());
341 w.push(b"\",\"codes_sha256\":\"");
342 w.push(self.codes_sha256.as_bytes());
343 w.push(b"\",\"cols\":");
344 w.push_u128(self.cols as u128);
345 w.push(b",\"rows\":");
346 w.push_u128(self.rows as u128);
347 w.push(b",\"spec\":\"");
348 w.push(self.spec.as_bytes());
349 w.push(b"\",\"tier\":\"");
350 w.push(self.tier.as_str().as_bytes());
351 w.push(b"\"}");
352
353 if w.overflowed {
354 return Err(KappaError::BufferTooSmall {
355 needed: w.at,
356 offered: w.buf.len(),
357 });
358 }
359 Ok(w.at)
360 }
361
362 /// The manifest for a coded matrix, given the two digests the caller has
363 /// computed over the bulk arrays.
364 pub fn of<E, Bd, C>(
365 matrix: &crate::CodedMatrix<'_, E, Bd, C>,
366 codebook_sha256: &'static str,
367 codes_sha256: &'static str,
368 spec: &'static str,
369 ) -> Manifest<'static>
370 where
371 E: uor_matmul_core::Element,
372 Bd: Bound,
373 C: crate::Codec<E, Bd>,
374 {
375 Manifest {
376 tier: C::TIER,
377 bound: Bd::VALUE,
378 rows: matrix.rows(),
379 cols: matrix.cols(),
380 block: C::MAX_BLOCK,
381 codebook_sha256,
382 codes_sha256,
383 spec,
384 }
385 }
386}
387
388fn is_sha256(s: &str) -> bool {
389 let Some(hex) = s.strip_prefix("sha256:") else {
390 return false;
391 };
392 hex.len() == 64
393 && hex
394 .bytes()
395 .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())
396}
397
398/// Address a manifest, writing the label into a caller-supplied buffer.
399///
400/// The label is `uor-addr-1`'s JCS-RFC8785 + NFC + SHA-256 transform applied to
401/// [`Manifest::write_canonical_json`]'s output.
402#[cfg(feature = "kappa")]
403pub fn address_into(
404 manifest: &Manifest<'_>,
405 scratch: &mut [u8],
406 out: &mut [u8; ADDRESS_LABEL_BYTES],
407) -> Result<(), KappaError> {
408 let n = manifest.write_canonical_json(scratch)?;
409 let outcome = uor_addr_1::address(&scratch[..n]).map_err(|_| KappaError::NotAddressable)?;
410 // `AddressOutcome::address`, not `.label`: the field is the ASCII wire form,
411 // `sha256:<64 lowercase hex>`, which is the 71 bytes `ADDRESS_LABEL_BYTES`
412 // names. This read `.label`, a field the crate does not have, and said so
413 // only when something built the `kappa` feature --- which nothing did.
414 let label = outcome.address.as_bytes();
415 if label.len() != ADDRESS_LABEL_BYTES {
416 return Err(KappaError::NotAddressable);
417 }
418 out.copy_from_slice(label);
419 Ok(())
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425
426 const D0: &str = "sha256:0000000000000000000000000000000000000000000000000000000000000000";
427 const D1: &str = "sha256:1111111111111111111111111111111111111111111111111111111111111111";
428 const D2: &str = "sha256:2222222222222222222222222222222222222222222222222222222222222222";
429
430 /// CK-08: the manifest is canonical --- keys in lexicographic order, no
431 /// whitespace --- so two independent producers of the same artifact write
432 /// the same bytes and therefore mint the same label.
433 #[test]
434 fn canonical_json_is_byte_stable_ck_08() {
435 let m = Manifest {
436 tier: TierId::Book,
437 bound: 127,
438 rows: 4096,
439 cols: 4096,
440 block: 8,
441 codebook_sha256: D0,
442 codes_sha256: D1,
443 spec: "uor-matmul/1",
444 };
445 let mut buf = [0u8; 512];
446 let n = m.write_canonical_json(&mut buf).unwrap();
447 let text = core::str::from_utf8(&buf[..n]).unwrap();
448 assert_eq!(
449 text,
450 concat!(
451 "{\"block\":8,\"bound\":127,",
452 "\"codebook_sha256\":\"sha256:00000000000000000000000000000000",
453 "00000000000000000000000000000000\",",
454 "\"codes_sha256\":\"sha256:11111111111111111111111111111111",
455 "11111111111111111111111111111111\",",
456 "\"cols\":4096,\"rows\":4096,\"spec\":\"uor-matmul/1\",\"tier\":\"Book\"}"
457 )
458 );
459 }
460
461 /// A short buffer reports what it needed rather than truncating, because a
462 /// truncated manifest would address a different artifact.
463 #[test]
464 fn a_short_buffer_reports_the_need_ck_08() {
465 let m = Manifest {
466 tier: TierId::Identity,
467 bound: 1,
468 rows: 1,
469 cols: 1,
470 block: 1,
471 codebook_sha256: D0,
472 codes_sha256: D1,
473 spec: "uor-matmul/1",
474 };
475 let mut buf = [0u8; 8];
476 match m.write_canonical_json(&mut buf) {
477 Err(KappaError::BufferTooSmall { needed, offered }) => {
478 assert!(needed > 8);
479 assert_eq!(offered, 8);
480 }
481 other => panic!("expected BufferTooSmall, got {other:?}"),
482 }
483 }
484
485 /// A malformed digest is rejected before anything is written: an artifact
486 /// whose bulk arrays are not addressed is not an addressable artifact.
487 #[test]
488 fn a_malformed_digest_is_rejected_ck_08() {
489 let m = Manifest {
490 tier: TierId::Identity,
491 bound: 1,
492 rows: 1,
493 cols: 1,
494 block: 1,
495 codebook_sha256: "not-a-digest",
496 codes_sha256: D1,
497 spec: "uor-matmul/1",
498 };
499 let mut buf = [0u8; 512];
500 assert_eq!(
501 m.write_canonical_json(&mut buf),
502 Err(KappaError::MalformedDigest)
503 );
504 }
505
506 /// CK-08: the arena tier's spelling is pinned like every other token. A
507 /// float alphabet has no magnitude, so the bound field is `Whole`'s value,
508 /// recorded as itself --- and a new token mints new addresses, which is the
509 /// point of the tier (CL-MM01).
510 #[test]
511 fn arena_manifest_spelling_is_byte_stable_ck_08() {
512 let m = Manifest {
513 tier: TierId::Arena,
514 bound: u128::MAX,
515 rows: 4096,
516 cols: 4096,
517 block: 1,
518 codebook_sha256: D0,
519 codes_sha256: D1,
520 spec: "uor-matmul/1",
521 };
522 let mut buf = [0u8; 512];
523 let n = m.write_canonical_json(&mut buf).unwrap();
524 let text = core::str::from_utf8(&buf[..n]).unwrap();
525 assert_eq!(
526 text,
527 concat!(
528 "{\"block\":1,\"bound\":340282366920938463463374607431768211455,",
529 "\"codebook_sha256\":\"sha256:00000000000000000000000000000000",
530 "00000000000000000000000000000000\",",
531 "\"codes_sha256\":\"sha256:11111111111111111111111111111111",
532 "11111111111111111111111111111111\",",
533 "\"cols\":4096,\"rows\":4096,\"spec\":\"uor-matmul/1\",\"tier\":\"Arena\"}"
534 )
535 );
536 }
537
538 /// `CS-10`: addressing is derived from the declaration, and the two fields
539 /// that move with the artifact's bytes are the two it does not read.
540 ///
541 /// Both directions, at the declaration level. A one-sided version --- only
542 /// that equal declarations address alike --- passes for a derivation that
543 /// returns a constant, so the second half asserts that each of the three
544 /// fields it *does* read moves the answer.
545 #[test]
546 fn addressing_is_read_from_the_declaration_cs_10() {
547 let e8 = Manifest {
548 tier: TierId::Book,
549 bound: 128,
550 rows: 4096,
551 cols: 4096,
552 block: 8,
553 codebook_sha256: D0,
554 codes_sha256: D1,
555 spec: "uor-matmul/1",
556 };
557
558 // Two artifacts, one declaration. Only the digests moved --- which is
559 // exactly what "the values changed" means to a manifest --- and the
560 // addressing did not.
561 let other = Manifest {
562 codebook_sha256: D2,
563 codes_sha256: D2,
564 ..e8
565 };
566 assert_ne!(e8, other, "different bytes are a different artifact");
567 let mut lhs = [0u8; 512];
568 let mut rhs = [0u8; 512];
569 let ln = e8.write_canonical_json(&mut lhs).unwrap();
570 let rn = other.write_canonical_json(&mut rhs).unwrap();
571 assert_ne!(lhs[..ln], rhs[..rn], "and a different canonical manifest");
572 assert_eq!(e8.addressing(), other.addressing());
573
574 // The run and the lane, recomputed here rather than recalled: eight
575 // products of two elements of magnitude 128 apiece, against the cap one
576 // narrow word holds, clamped where a 32-bit machine cannot index that
577 // far.
578 let want_run = (NARROW_CAP / (8 * 128 * 128)).min(usize::MAX as u128) as usize;
579 assert_eq!(
580 e8.addressing(),
581 Addressing::ARunOf {
582 elements: 8,
583 lane_run: Some(want_run),
584 }
585 );
586 assert!(e8.addressing().addresses_a_run());
587 assert!(e8.addressing().addresses_an_element());
588
589 // The block moves it. One element per code addresses an element and not
590 // a run, which is the term the tabulated break-even refuses.
591 let scalar = Manifest { block: 1, ..e8 };
592 assert!(scalar.addressing().addresses_an_element());
593 assert!(!scalar.addressing().addresses_a_run());
594 assert_ne!(scalar.addressing(), e8.addressing());
595 assert_eq!(Addressing::of(TierId::Book, 0, 128), Addressing::Nothing);
596
597 // The bound moves it. `Whole`'s `u128::MAX` is not a magnitude, so no
598 // run of any length fits a narrow word and the partial sums are carried
599 // in the exact accumulator --- which is what the arena tier declares.
600 assert_eq!(
601 Addressing::of(TierId::Arena, 1, u128::MAX),
602 Addressing::ARunOf {
603 elements: 1,
604 lane_run: None,
605 }
606 );
607 // And so does a bound one block of which already exceeds the lane.
608 assert_eq!(
609 Addressing::of(TierId::Book, 8, 1u128 << 40),
610 Addressing::ARunOf {
611 elements: 8,
612 lane_run: None,
613 }
614 );
615 // A bound of zero is the alphabet `{0}`: nothing fills the lane, ever.
616 assert_eq!(
617 Addressing::of(TierId::Book, 8, 0),
618 Addressing::ARunOf {
619 elements: 8,
620 lane_run: Some(usize::MAX),
621 }
622 );
623
624 // The tier moves it, for the two tiers with nothing between a code and
625 // an element --- whatever their block and bound say.
626 assert_eq!(
627 Addressing::of(TierId::Identity, 1, 128),
628 Addressing::Nothing
629 );
630 assert_eq!(Addressing::of(TierId::Runs, 8, 128), Addressing::Nothing);
631
632 // Orientation, read from `rows` and `cols` and from nothing else, at a
633 // shape where `k != n` so the two are distinguishable.
634 let shape = Shape { m: 3, k: 64, n: 40 };
635 let along = Manifest {
636 rows: 40,
637 cols: 64,
638 ..e8
639 };
640 let across = Manifest {
641 rows: 64,
642 cols: 40,
643 ..e8
644 };
645 assert!(along.reduces_along_the_block(shape));
646 assert!(!along.reduces_across_the_block(shape));
647 assert!(across.reduces_across_the_block(shape));
648 assert!(!across.reduces_along_the_block(shape));
649 // The two differ in `rows` and `cols` alone, so the orientation came
650 // from the declaration and the code declaration is untouched by it.
651 assert_eq!(along.addressing(), across.addressing());
652
653 // A square coded operand answers both, because at `k == n` the
654 // declaration names no difference and neither answer is wrong.
655 let square = Shape { m: 3, k: 64, n: 64 };
656 let s = Manifest {
657 rows: 64,
658 cols: 64,
659 ..e8
660 };
661 assert!(s.reduces_along_the_block(square));
662 assert!(s.reduces_across_the_block(square));
663 }
664}