digit_layout/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![deny(warnings, missing_docs)]
4
5#[macro_use]
6mod macros;
7pub mod types;
8
9#[cfg(test)]
10extern crate alloc;
11
12/// A layout of a digit data type in memory.
13#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
14#[repr(C)]
15pub struct DigitLayout {
16    code: u32,
17    group: u16,
18    size: u16,
19}
20
21/// The content of a digit layout.
22#[derive(Clone, Copy, PartialEq, Eq, Debug)]
23pub enum LayoutContent {
24    /// An unsigned integer type.
25    Unsigned {
26        /// The width of the integer in bits.
27        width: u32,
28    },
29    /// A real number type.
30    Real {
31        /// The width of the exponent in bits.
32        exponent: u32,
33        /// The width of the mantissa in bits.
34        mantissa: u32,
35    },
36    /// A named type.
37    Named {
38        /// The name of the type.
39        name: [u8; 8],
40    },
41}
42
43#[repr(u32)]
44enum DigitLayoutType {
45    Unsigned = 0xE000_0000, // 0b111...
46    Real = 0xC000_0000,     // 0b110...
47    Named = 0,              // 0b...
48}
49const UNSIGNED: u32 = DigitLayoutType::Unsigned as _;
50const SIGNED: u32 = DigitLayoutType::Real as _;
51const HEAD: u32 = UNSIGNED;
52
53impl DigitLayout {
54    /// Create a new digit layout for an unsigned integer type.
55    #[inline]
56    pub const fn unsigned(width: u16, group: u16) -> Self {
57        assert!(width.is_power_of_two() && width >= 8);
58
59        let body = width as u32;
60        assert!(body & HEAD == 0);
61        Self::new(DigitLayoutType::Unsigned, body, group, width / 8 * group)
62    }
63
64    /// Create a new digit layout for a real number type.
65    #[inline]
66    pub const fn real(exponent: u16, mantissa: u16, group: u16) -> Self {
67        let width = 1 + exponent + mantissa;
68        assert!(width.is_power_of_two() && width >= 8);
69
70        let body = ((exponent as u32) << 16) | mantissa as u32;
71        assert!(body & HEAD == 0);
72        Self::new(DigitLayoutType::Real, body, group, width / 8 * group)
73    }
74
75    /// Create a new digit layout for a named type.
76    pub const fn named(name: &str, group: u16, size: u16) -> Self {
77        let mut exp = 1;
78        let mut bytes = name.as_bytes();
79        let mut body = 0;
80        while let [b, tail @ ..] = bytes {
81            bytes = tail;
82
83            let b = match b {
84                b'0'..=b'9' => *b - b'0',
85                b'a'..=b'z' => *b - b'a' + 10,
86                b'A'..=b'Z' => *b - b'A' + 10,
87                b'_' | b'.' => continue,
88                _ => panic!("Invalid character in digit name"),
89            };
90            body += (b as u32 + 1) * exp;
91            const GUARD: u32 = 0xC000_0000; // 0b110...
92            assert!(body & GUARD != GUARD);
93            assert!(exp & GUARD != GUARD);
94            exp *= 37; // 37 = 10 + 26 + 1
95        }
96        Self::new(DigitLayoutType::Named, body, group, size)
97    }
98
99    #[inline(always)]
100    const fn new(ty: DigitLayoutType, body: u32, group: u16, size: u16) -> Self {
101        Self {
102            code: (ty as u32) | body,
103            group,
104            size,
105        }
106    }
107
108    /// Raw transmutation to `u64`.
109    #[inline]
110    pub const fn to_u64(self) -> u64 {
111        unsafe { core::mem::transmute(self) }
112    }
113
114    /// Get the number of bytes occupied by this layout.
115    pub const fn group_size(self) -> usize {
116        self.group as _
117    }
118
119    /// Get the number of bytes occupied by this layout.
120    pub const fn nbytes(self) -> usize {
121        self.size as usize
122    }
123
124    /// Decode the content of the digit layout.
125    pub const fn decode(self) -> LayoutContent {
126        let head = self.code & HEAD;
127        match head {
128            UNSIGNED => LayoutContent::Unsigned {
129                width: self.decode_unsigned(),
130            },
131            SIGNED => LayoutContent::Real {
132                exponent: self.decode_exponent(),
133                mantissa: self.decode_mantissa(),
134            },
135            _ => {
136                let mut name = [0; 8];
137                let mut body = self.code;
138                let mut i = 0;
139                while body > 0 {
140                    let b = (body % 37) as u8 - 1;
141                    name[i] = b + if b < 10 { b'0' } else { b'a' - 10 };
142                    body /= 37;
143                    i += 1;
144                }
145                LayoutContent::Named { name }
146            }
147        }
148    }
149
150    #[inline(always)]
151    const fn decode_unsigned(self) -> u32 {
152        self.code & !HEAD
153    }
154
155    #[inline(always)]
156    const fn decode_exponent(self) -> u32 {
157        ((self.code & !HEAD) >> 16) & 0xFF
158    }
159
160    #[inline(always)]
161    const fn decode_mantissa(self) -> u32 {
162        self.code & 0xFFFF
163    }
164}
165
166use core::fmt;
167
168impl fmt::Display for DigitLayout {
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        use LayoutContent::*;
171        match self.decode() {
172            Unsigned { width } => {
173                if self.group == 1 {
174                    write!(f, "u{width}")
175                } else {
176                    write!(f, "[u{width}; {}]", self.group)
177                }
178            }
179            Real { exponent, mantissa } => {
180                let width = 1 + exponent + mantissa;
181                if self.group == 1 {
182                    write!(f, "f{width}_e{exponent}m{mantissa}")
183                } else {
184                    write!(f, "[f{width}_e{exponent}m{mantissa}; {}]", self.group)
185                }
186            }
187            Named { name } => {
188                for c in name {
189                    if c == 0 {
190                        break;
191                    }
192                    write!(f, "{}", c as char)?;
193                }
194                Ok(())
195            }
196        }
197    }
198}
199
200#[test]
201fn test_unsigned() {
202    assert!(matches!(
203        types::U8.decode(),
204        LayoutContent::Unsigned { width: 8 }
205    ));
206
207    assert!(matches!(
208        types::U16.decode(),
209        LayoutContent::Unsigned { width: 16 }
210    ));
211
212    assert!(matches!(
213        types::U32.decode(),
214        LayoutContent::Unsigned { width: 32 }
215    ));
216
217    assert!(matches!(
218        types::U64.decode(),
219        LayoutContent::Unsigned { width: 64 }
220    ));
221}
222
223#[test]
224fn test_real() {
225    assert!(matches!(
226        types::I8.decode(),
227        LayoutContent::Real {
228            exponent: 0,
229            mantissa: 7,
230        }
231    ));
232
233    assert!(matches!(
234        types::I16.decode(),
235        LayoutContent::Real {
236            exponent: 0,
237            mantissa: 15,
238        }
239    ));
240
241    assert!(matches!(
242        types::I32.decode(),
243        LayoutContent::Real {
244            exponent: 0,
245            mantissa: 31,
246        }
247    ));
248
249    assert!(matches!(
250        types::I64.decode(),
251        LayoutContent::Real {
252            exponent: 0,
253            mantissa: 63,
254        }
255    ));
256
257    assert!(matches!(
258        types::F16.decode(),
259        LayoutContent::Real {
260            exponent: 5,
261            mantissa: 10,
262        }
263    ));
264
265    assert!(matches!(
266        types::BF16.decode(),
267        LayoutContent::Real {
268            exponent: 8,
269            mantissa: 7,
270        }
271    ));
272
273    assert!(matches!(
274        types::F32.decode(),
275        LayoutContent::Real {
276            exponent: 8,
277            mantissa: 23,
278        }
279    ));
280
281    assert!(matches!(
282        types::F64.decode(),
283        LayoutContent::Real {
284            exponent: 11,
285            mantissa: 52,
286        }
287    ));
288}
289
290#[test]
291fn test_named() {
292    assert!(matches!(
293        types::Bool.decode(),
294        LayoutContent::Named {
295            name: [b'b', b'o', b'o', b'l', 0, 0, 0, 0]
296        }
297    ));
298
299    let q8_0 = DigitLayout::named("Q8_0", 32, 34);
300    assert!(matches!(
301        q8_0.decode(),
302        LayoutContent::Named {
303            name: [b'q', b'8', b'0', 0, 0, 0, 0, 0]
304        }
305    ));
306
307    let iq2xxs = DigitLayout::named("IQ2XXS", 256, 66);
308    assert!(matches!(
309        iq2xxs.decode(),
310        LayoutContent::Named {
311            name: [b'i', b'q', b'2', b'x', b'x', b's', 0, 0]
312        }
313    ));
314
315    let zzzzzz = DigitLayout::named("zzzzzz", 1, 1);
316    assert!(matches!(
317        zzzzzz.decode(),
318        LayoutContent::Named {
319            name: [b'z', b'z', b'z', b'z', b'z', b'z', 0, 0]
320        }
321    ));
322}
323
324#[test]
325fn test_decode_methods() {
326    // 测试decode_unsigned方法
327    let u8_layout = DigitLayout::unsigned(8, 1);
328    assert_eq!(u8_layout.decode_unsigned(), 8);
329
330    let u16_layout = DigitLayout::unsigned(16, 1);
331    assert_eq!(u16_layout.decode_unsigned(), 16);
332
333    let f32_layout = DigitLayout::real(8, 23, 1);
334    assert_eq!(f32_layout.decode_exponent(), 8);
335    assert_eq!(f32_layout.decode_mantissa(), 23);
336
337    let f64_layout = DigitLayout::real(11, 52, 1);
338    assert_eq!(f64_layout.decode_exponent(), 11);
339    assert_eq!(f64_layout.decode_mantissa(), 52);
340}
341
342#[test]
343fn test_group_size_and_nbytes() {
344    // 测试group_size和nbytes方法
345    let layout1 = DigitLayout::unsigned(32, 4);
346    assert_eq!(layout1.group_size(), 4);
347    assert_eq!(layout1.nbytes(), 16); // 32/8 * 4 = 16
348
349    let layout2 = DigitLayout::real(8, 23, 2);
350    assert_eq!(layout2.group_size(), 2);
351    assert_eq!(layout2.nbytes(), 8); // (1+8+23)/8 * 2 = 8
352
353    let layout3 = DigitLayout::named("test", 3, 12);
354    assert_eq!(layout3.group_size(), 3);
355    assert_eq!(layout3.nbytes(), 12);
356}
357
358#[test]
359fn test_to_u64() {
360    let layout = DigitLayout::unsigned(32, 1);
361    let u64_value = layout.to_u64();
362    assert_ne!(u64_value, 0);
363
364    let same_layout = DigitLayout::unsigned(32, 1);
365    assert_eq!(layout.to_u64(), same_layout.to_u64());
366    let different_layout = DigitLayout::unsigned(64, 1);
367    assert_ne!(layout.to_u64(), different_layout.to_u64());
368}
369
370#[test]
371fn test_display_impl() {
372    use alloc::string::String;
373    use core::fmt::Write;
374
375    struct TestWriter(String);
376
377    impl Write for TestWriter {
378        fn write_str(&mut self, s: &str) -> core::fmt::Result {
379            self.0.push_str(s);
380            Ok(())
381        }
382    }
383
384    let u8_layout = DigitLayout::unsigned(8, 1);
385    let mut writer = TestWriter(String::new());
386    write!(writer, "{}", u8_layout).unwrap();
387    assert_eq!(writer.0, "u8");
388
389    let u8_array_layout = DigitLayout::unsigned(8, 4);
390    let mut writer = TestWriter(String::new());
391    write!(writer, "{}", u8_array_layout).unwrap();
392    assert_eq!(writer.0, "[u8; 4]");
393
394    let f32_layout = DigitLayout::real(8, 23, 1);
395    let mut writer = TestWriter(String::new());
396    write!(writer, "{}", f32_layout).unwrap();
397    assert_eq!(writer.0, "f32_e8m23");
398
399    let f32_array_layout = DigitLayout::real(8, 23, 2);
400    let mut writer = TestWriter(String::new());
401    write!(writer, "{}", f32_array_layout).unwrap();
402    assert_eq!(writer.0, "[f32_e8m23; 2]");
403
404    let named_layout = DigitLayout::named("test", 1, 4);
405    let mut writer = TestWriter(String::new());
406    write!(writer, "{}", named_layout).unwrap();
407    assert_eq!(writer.0, "test");
408}
409
410#[test]
411fn test_named_edge_cases() {
412    use alloc::string::String;
413    use core::fmt::Write;
414
415    struct TestWriter(String);
416
417    impl Write for TestWriter {
418        fn write_str(&mut self, s: &str) -> core::fmt::Result {
419            self.0.push_str(s);
420            Ok(())
421        }
422    }
423
424    let empty_name = DigitLayout::named("", 1, 1);
425    let mut writer = TestWriter(String::new());
426    let _ = write!(writer, "{}", empty_name);
427
428    let alphanumeric = DigitLayout::named("a1B2c3", 1, 1);
429    assert!(matches!(
430        alphanumeric.decode(),
431        LayoutContent::Named {
432            name: [b'a', b'1', b'b', b'2', b'c', b'3', 0, 0]
433        }
434    ));
435
436    let with_special = DigitLayout::named("a_b.c", 1, 1);
437    assert!(matches!(
438        with_special.decode(),
439        LayoutContent::Named {
440            name: [b'a', b'b', b'c', 0, 0, 0, 0, 0]
441        }
442    ));
443}