Skip to main content

subetha_core/
axis_signature.rs

1//! `AxisSignature` - direction-signature catalog for the SubEtha
2//! design cube, spanning both the concurrent-data-structure domain
3//! (deque variants) and the exotic-pointer domain.
4//!
5//! Every variant in the MMF-deque family AND every exotic pointer
6//! type has a constrained **direction signature**: the set of axis
7//! values it engages at a non-default value. The dispatcher routes
8//! per call by satisfying the workload's required signature against
9//! the available variants' signatures.
10//!
11//! This module names the axes of the design cube and defines the
12//! `AxisMask` bitmask type that lets variants declare their
13//! engagement on each axis. The dispatcher tests
14//! `provided.satisfies(required)` to pick a routable variant.
15//!
16//! ## The deque-domain axes (bits 0..=5)
17//!
18//! 1. **K_inner**: items per slot. 1 or 3.
19//! 2. **K_outer**: slots per producer-counter atomic. 1 or K.
20//! 3. **K_consumer**: mailboxes per thief. shared or N per-thief.
21//! 4. **K_counter_share**: producer counter ownership. shared or owner-private.
22//! 5. **K_radius**: coherence distance of publish. Local or Distant (CPUID-dispatched).
23//! 6. **K_gating**: synchronisation granularity. counter-only or per-slot.
24//!
25//! ## The pointer-domain axes (bits 6..=12)
26//!
27//! 7. **K_stride**: stride encoded in shift count (kstep-style).
28//! 8. **K_segmented**: multi-segment address space (k-tower-style).
29//! 9. **K_content_prefix**: content-summary stored at slot (umbra,
30//!    bloom, cardinality).
31//! 10. **K_type_tag**: type discriminant stored at slot (self-desc).
32//! 11. **K_version**: version metadata stored at slot.
33//! 12. **K_async**: future / async-state stored at slot.
34//! 13. **K_bounds**: runtime bounds metadata at slot (cheri-style).
35
36#![allow(clippy::missing_errors_doc)]
37
38use core::fmt;
39
40/// The axes of the SubEtha design cube. Bits 0..=5 are the
41/// concurrent-data-structure (deque) axes; bits 6..=12 are the
42/// exotic-pointer axes.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub enum Axis {
45    /// Items per slot (K_inner). Deque-domain.
46    Inner,
47    /// Slots per producer-counter atomic (K_outer). Deque-domain.
48    Outer,
49    /// Mailboxes per thief (K_consumer). Deque-domain.
50    Consumer,
51    /// Producer counter ownership (K_counter_share). Deque-domain.
52    CounterShare,
53    /// Coherence distance of publish (K_radius). Deque-domain.
54    Radius,
55    /// Per-slot atomic vs counter-only (K_gating). Deque-domain.
56    Gating,
57    /// Stride encoded in shift count (K_stride). Pointer-domain.
58    Stride,
59    /// Multi-segment address space (K_segmented). Pointer-domain.
60    Segmented,
61    /// Content-summary stored at slot (K_content_prefix).
62    /// Pointer-domain. Engaged by umbra / bloom / cardinality.
63    ContentPrefix,
64    /// Type discriminant stored at slot (K_type_tag). Pointer-domain.
65    TypeTag,
66    /// Version metadata stored at slot (K_version). Pointer-domain.
67    Version,
68    /// Future / async-state stored at slot (K_async). Pointer-domain.
69    Async,
70    /// Runtime bounds metadata stored at slot (K_bounds).
71    /// Pointer-domain.
72    Bounds,
73}
74
75impl Axis {
76    /// All axes in canonical order.
77    pub const ALL: [Axis; 13] = [
78        Axis::Inner,
79        Axis::Outer,
80        Axis::Consumer,
81        Axis::CounterShare,
82        Axis::Radius,
83        Axis::Gating,
84        Axis::Stride,
85        Axis::Segmented,
86        Axis::ContentPrefix,
87        Axis::TypeTag,
88        Axis::Version,
89        Axis::Async,
90        Axis::Bounds,
91    ];
92
93    /// Bit position of this axis in the packed `AxisMask` `u16`
94    /// representation. Bits 0..=5 are deque-domain; bits 6..=12 are
95    /// pointer-domain.
96    #[inline(always)]
97    pub const fn bit(self) -> u16 {
98        match self {
99            Axis::Inner => 0,
100            Axis::Outer => 1,
101            Axis::Consumer => 2,
102            Axis::CounterShare => 3,
103            Axis::Radius => 4,
104            Axis::Gating => 5,
105            Axis::Stride => 6,
106            Axis::Segmented => 7,
107            Axis::ContentPrefix => 8,
108            Axis::TypeTag => 9,
109            Axis::Version => 10,
110            Axis::Async => 11,
111            Axis::Bounds => 12,
112        }
113    }
114}
115
116impl fmt::Display for Axis {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        let name = match self {
119            Axis::Inner => "K_inner",
120            Axis::Outer => "K_outer",
121            Axis::Consumer => "K_consumer",
122            Axis::CounterShare => "K_counter_share",
123            Axis::Radius => "K_radius",
124            Axis::Gating => "K_gating",
125            Axis::Stride => "K_stride",
126            Axis::Segmented => "K_segmented",
127            Axis::ContentPrefix => "K_content_prefix",
128            Axis::TypeTag => "K_type_tag",
129            Axis::Version => "K_version",
130            Axis::Async => "K_async",
131            Axis::Bounds => "K_bounds",
132        };
133        f.write_str(name)
134    }
135}
136
137/// A bitmask over the design-cube axes. Bit `i` set means the
138/// corresponding axis is engaged at its non-default value.
139///
140/// The direction signature for a variant is its `AxisMask`: which
141/// axes it sets to non-default values. A workload's required
142/// signature is also an `AxisMask`: which axes it needs the
143/// transport (or pointer type) to handle non-trivially.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
145pub struct AxisMask(u16);
146
147impl AxisMask {
148    /// The mask covering every defined axis. Bits 0..=12 are valid.
149    const VALID_BITS: u16 = (1u16 << 13) - 1;
150
151    /// The empty signature (no axes engaged). Corresponds to the
152    /// origin corner of the cube (Chase-Lev's signature).
153    pub const EMPTY: AxisMask = AxisMask(0);
154
155    /// All defined axes engaged.
156    pub const ALL: AxisMask = AxisMask(Self::VALID_BITS);
157
158    /// Build a signature from a slice of engaged axes.
159    pub const fn from_axes(axes: &[Axis]) -> Self {
160        let mut bits = 0u16;
161        let mut i = 0;
162        while i < axes.len() {
163            bits |= 1u16 << axes[i].bit();
164            i += 1;
165        }
166        AxisMask(bits)
167    }
168
169    /// Build from a raw `u16`. Bits outside the valid range are
170    /// masked off.
171    pub const fn from_bits(bits: u16) -> Self {
172        AxisMask(bits & Self::VALID_BITS)
173    }
174
175    /// Return the raw `u16` representation.
176    #[inline(always)]
177    pub const fn bits(self) -> u16 {
178        self.0
179    }
180
181    /// `true` if axis `a` is engaged in this signature.
182    #[inline(always)]
183    pub const fn contains(self, a: Axis) -> bool {
184        (self.0 >> a.bit()) & 1 == 1
185    }
186
187    /// Count of engaged axes (popcount of the bitmask).
188    #[inline(always)]
189    pub const fn count(self) -> u32 {
190        self.0.count_ones()
191    }
192
193    /// Union of two signatures.
194    #[inline(always)]
195    pub const fn union(self, other: AxisMask) -> AxisMask {
196        AxisMask(self.0 | other.0)
197    }
198
199    /// Intersection of two signatures.
200    #[inline(always)]
201    pub const fn intersection(self, other: AxisMask) -> AxisMask {
202        AxisMask(self.0 & other.0)
203    }
204
205    /// `true` if this signature is a superset of `other` (i.e. every
206    /// axis required by `other` is engaged in `self`).
207    ///
208    /// `provided.satisfies(required)` means the variant `provided`
209    /// can transport the workload `required`.
210    #[inline(always)]
211    pub const fn satisfies(self, other: AxisMask) -> bool {
212        (self.0 & other.0) == other.0
213    }
214
215    /// Hamming distance between two signatures: how many axes
216    /// differ. This is the cube-distance the dispatcher pays when
217    /// routing a workload to a non-perfect-match variant.
218    #[inline(always)]
219    pub const fn distance(self, other: AxisMask) -> u32 {
220        (self.0 ^ other.0).count_ones()
221    }
222}
223
224impl fmt::Display for AxisMask {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        let mut first = true;
227        f.write_str("{")?;
228        for a in Axis::ALL {
229            if self.contains(a) {
230                if !first {
231                    f.write_str(", ")?;
232                }
233                fmt::Display::fmt(&a, f)?;
234                first = false;
235            }
236        }
237        f.write_str("}")
238    }
239}
240
241/// Cross-axis fusions a variant implements. Each fusion is an
242/// `AxisMask` of two or three axes whose state is packed into one
243/// atomic word (for ≤8 B fusion) or one 64-byte cache-line publish
244/// (for MOVDIR64B-based corner fusion).
245///
246/// Terminology: a 2-axis fusion packs two axes' state into one
247/// atomic word; a 3-axis fusion publishes three axes' state in one
248/// instruction; a 4-axis fusion is the maximum on a single cache
249/// line.
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub struct Fusion {
252    /// The axes packed into the fused atomic word.
253    pub axes: AxisMask,
254}
255
256impl Fusion {
257    /// Build a 2-axis fusion.
258    pub const fn pair(a: Axis, b: Axis) -> Self {
259        Self {
260            axes: AxisMask::from_axes(&[a, b]),
261        }
262    }
263
264    /// Build a 3-axis fusion.
265    pub const fn triple(a: Axis, b: Axis, c: Axis) -> Self {
266        Self {
267            axes: AxisMask::from_axes(&[a, b, c]),
268        }
269    }
270
271    /// Number of axes engaged in this fusion (2 for pairs, 3 for
272    /// triples, etc.).
273    #[inline(always)]
274    pub const fn axis_count(self) -> u32 {
275        self.axes.count()
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn empty_signature_contains_nothing() {
285        assert!(!AxisMask::EMPTY.contains(Axis::Inner));
286        assert_eq!(AxisMask::EMPTY.count(), 0);
287    }
288
289    #[test]
290    fn all_signature_contains_everything() {
291        for a in Axis::ALL {
292            assert!(AxisMask::ALL.contains(a));
293        }
294        assert_eq!(AxisMask::ALL.count(), Axis::ALL.len() as u32);
295    }
296
297    #[test]
298    fn from_axes_packs_bits_correctly() {
299        let s = AxisMask::from_axes(&[Axis::Inner, Axis::Gating]);
300        assert!(s.contains(Axis::Inner));
301        assert!(s.contains(Axis::Gating));
302        assert!(!s.contains(Axis::Outer));
303        assert_eq!(s.count(), 2);
304    }
305
306    #[test]
307    fn satisfies_is_superset_check() {
308        let chase_lev = AxisMask::EMPTY;
309        let khl = AxisMask::from_axes(&[
310            Axis::Inner,
311            Axis::Outer,
312            Axis::CounterShare,
313            Axis::Radius,
314            Axis::Gating,
315        ]);
316        let request_reply = AxisMask::EMPTY;
317        let producer_fast = AxisMask::from_axes(&[Axis::Inner, Axis::Outer]);
318
319        assert!(chase_lev.satisfies(request_reply));
320        assert!(khl.satisfies(request_reply));
321        assert!(khl.satisfies(producer_fast));
322        assert!(!chase_lev.satisfies(producer_fast));
323    }
324
325    #[test]
326    fn distance_counts_differing_axes() {
327        let a = AxisMask::from_axes(&[Axis::Inner, Axis::Outer]);
328        let b = AxisMask::from_axes(&[Axis::Inner, Axis::Gating]);
329        // Outer and Gating differ; Inner agrees.
330        assert_eq!(a.distance(b), 2);
331        assert_eq!(a.distance(a), 0);
332        assert_eq!(
333            AxisMask::EMPTY.distance(AxisMask::ALL),
334            Axis::ALL.len() as u32
335        );
336    }
337
338    #[test]
339    fn pair_fusion_has_axis_count_two() {
340        let f = Fusion::pair(Axis::Inner, Axis::Gating);
341        assert_eq!(f.axis_count(), 2);
342    }
343
344    #[test]
345    fn triple_fusion_has_axis_count_three() {
346        let f = Fusion::triple(Axis::Radius, Axis::Inner, Axis::Gating);
347        assert_eq!(f.axis_count(), 3);
348    }
349
350    #[test]
351    fn display_renders_axis_names() {
352        let s = AxisMask::from_axes(&[Axis::Inner, Axis::Radius]);
353        let rendered = format!("{s}");
354        assert!(rendered.contains("K_inner"));
355        assert!(rendered.contains("K_radius"));
356    }
357}