Skip to main content

serializer/machine/
slot.rs

1//! DX-Machine unified slot format
2//!
3//! The 16-byte slot is the core innovation that enables inline optimization.
4//! It can store either inline data (≤14 bytes) or a heap reference.
5
6use std::fmt;
7
8/// Maximum bytes that can be stored inline
9pub const MAX_INLINE_SIZE: usize = 14;
10
11/// Marker byte for inline data (byte 15 = 0x00)
12pub const INLINE_MARKER: u8 = 0x00;
13
14/// Marker byte for heap reference (byte 15 = 0xFF)
15pub const HEAP_MARKER: u8 = 0xFF;
16
17/// 16-byte slot that holds either inline data or heap reference
18#[repr(C, packed)]
19#[derive(Clone, Copy)]
20pub struct DxMachineSlot {
21    /// Raw slot data (16 bytes)
22    ///
23    /// Layout depends on marker byte (byte 15):
24    ///
25    /// **Inline (marker = 0x00):**
26    /// - `[0]`:     length (0-14)
27    /// - `[1-14]`:  inline data
28    /// - `[15]`:    0x00 (INLINE\_MARKER)
29    ///
30    /// **Heap (marker = 0xFF):**
31    /// - `[0-3]`:   heap offset (u32 LE)
32    /// - `[4-7]`:   data length (u32 LE)
33    /// - `[8-14]`:  reserved (zero)
34    /// - `[15]`:    0xFF (HEAP\_MARKER)
35    pub data: [u8; 16],
36}
37
38impl DxMachineSlot {
39    /// Create empty slot (inline, zero length)
40    #[inline]
41    pub const fn new() -> Self {
42        Self { data: [0; 16] }
43    }
44
45    /// Create inline slot from data (≤14 bytes)
46    #[inline]
47    pub fn inline_from_bytes(bytes: &[u8]) -> Result<Self, SlotError> {
48        let len = bytes.len();
49        if len > MAX_INLINE_SIZE {
50            return Err(SlotError::DataTooLarge {
51                size: len,
52                max: MAX_INLINE_SIZE,
53            });
54        }
55
56        let mut slot = Self::new();
57        slot.data[0] = len as u8;
58        slot.data[1..1 + len].copy_from_slice(bytes);
59        slot.data[15] = INLINE_MARKER;
60
61        Ok(slot)
62    }
63
64    /// Create heap reference slot
65    #[inline]
66    pub fn heap_reference(offset: u32, length: u32) -> Self {
67        let mut slot = Self::new();
68        slot.data[0..4].copy_from_slice(&offset.to_le_bytes());
69        slot.data[4..8].copy_from_slice(&length.to_le_bytes());
70        slot.data[15] = HEAP_MARKER;
71        slot
72    }
73
74    /// Check if slot contains inline data
75    #[inline]
76    pub const fn is_inline(&self) -> bool {
77        self.data[15] == INLINE_MARKER
78    }
79
80    /// Check if slot is heap reference
81    #[inline]
82    pub const fn is_heap(&self) -> bool {
83        self.data[15] == HEAP_MARKER
84    }
85
86    /// Get inline data length (panics if not inline)
87    #[inline]
88    pub const fn inline_len(&self) -> usize {
89        debug_assert!(self.is_inline());
90        self.data[0] as usize
91    }
92
93    /// Get inline data (panics if not inline)
94    #[inline]
95    pub fn inline_data(&self) -> &[u8] {
96        debug_assert!(self.is_inline());
97        let len = self.inline_len();
98        &self.data[1..1 + len]
99    }
100
101    /// Get inline data as UTF-8 string (panics if not inline or invalid UTF-8)
102    ///
103    /// # Panics
104    ///
105    /// Panics if the inline data is not valid UTF-8. Use [`Self::inline_str_checked`]
106    /// for fallible conversion.
107    #[inline]
108    #[allow(clippy::expect_used)] // Intentional panic - documented behavior, use inline_str_checked() for fallible version
109    pub fn inline_str(&self) -> &str {
110        debug_assert!(self.is_inline());
111        let data = self.inline_data();
112        // SAFETY: This function is documented to panic on invalid UTF-8.
113        // Callers should use inline_str_checked() for fallible conversion.
114        // The expect message provides context for debugging if this occurs.
115        std::str::from_utf8(data).expect("Invalid UTF-8 in inline data")
116    }
117
118    /// Get inline data as UTF-8 string, returning None if invalid UTF-8
119    ///
120    /// This is the fallible version of [`Self::inline_str`].
121    #[inline]
122    pub fn inline_str_checked(&self) -> Option<&str> {
123        if !self.is_inline() {
124            return None;
125        }
126        let data = self.inline_data();
127        std::str::from_utf8(data).ok()
128    }
129
130    /// Get heap offset (panics if not heap)
131    #[inline]
132    pub fn heap_offset(&self) -> u32 {
133        debug_assert!(self.is_heap());
134        u32::from_le_bytes([self.data[0], self.data[1], self.data[2], self.data[3]])
135    }
136
137    /// Get heap data length (panics if not heap)
138    #[inline]
139    pub fn heap_length(&self) -> u32 {
140        debug_assert!(self.is_heap());
141        u32::from_le_bytes([self.data[4], self.data[5], self.data[6], self.data[7]])
142    }
143
144    /// Get heap reference (panics if not heap)
145    #[inline]
146    pub fn heap_ref(&self) -> (u32, u32) {
147        debug_assert!(self.is_heap());
148        (self.heap_offset(), self.heap_length())
149    }
150
151    /// Write inline data to slot
152    #[inline]
153    pub fn write_inline(&mut self, bytes: &[u8]) -> Result<(), SlotError> {
154        let len = bytes.len();
155        if len > MAX_INLINE_SIZE {
156            return Err(SlotError::DataTooLarge {
157                size: len,
158                max: MAX_INLINE_SIZE,
159            });
160        }
161
162        self.data[0] = len as u8;
163        self.data[1..1 + len].copy_from_slice(bytes);
164        self.data[15] = INLINE_MARKER;
165
166        Ok(())
167    }
168
169    /// Write heap reference to slot
170    #[inline]
171    pub fn write_heap(&mut self, offset: u32, length: u32) {
172        self.data[0..4].copy_from_slice(&offset.to_le_bytes());
173        self.data[4..8].copy_from_slice(&length.to_le_bytes());
174        self.data[8..15].fill(0);
175        self.data[15] = HEAP_MARKER;
176    }
177
178    /// Compare inline data with byte slice (optimized)
179    #[inline]
180    pub fn eq_inline_bytes(&self, other: &[u8]) -> bool {
181        if !self.is_inline() {
182            return false;
183        }
184
185        let len = self.inline_len();
186        if len != other.len() {
187            return false;
188        }
189
190        self.inline_data() == other
191    }
192
193    /// Compare inline data with string (optimized)
194    #[inline]
195    pub fn eq_inline_str(&self, other: &str) -> bool {
196        self.eq_inline_bytes(other.as_bytes())
197    }
198
199    /// Get data size (inline or heap)
200    #[inline]
201    pub fn size(&self) -> usize {
202        if self.is_inline() {
203            self.inline_len()
204        } else {
205            self.heap_length() as usize
206        }
207    }
208}
209
210impl Default for DxMachineSlot {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216impl fmt::Debug for DxMachineSlot {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        if self.is_inline() {
219            let len = self.inline_len();
220            let data = self.inline_data();
221            if let Ok(s) = std::str::from_utf8(data) {
222                f.debug_struct("DxMachineSlot")
223                    .field("type", &"inline")
224                    .field("len", &len)
225                    .field("data", &s)
226                    .finish()
227            } else {
228                f.debug_struct("DxMachineSlot")
229                    .field("type", &"inline")
230                    .field("len", &len)
231                    .field("data", &data)
232                    .finish()
233            }
234        } else if self.is_heap() {
235            f.debug_struct("DxMachineSlot")
236                .field("type", &"heap")
237                .field("offset", &self.heap_offset())
238                .field("length", &self.heap_length())
239                .finish()
240        } else {
241            f.debug_struct("DxMachineSlot")
242                .field("type", &"invalid")
243                .field("marker", &self.data[15])
244                .finish()
245        }
246    }
247}
248
249/// Slot operation errors
250#[derive(Debug, Clone, PartialEq, Eq)]
251pub enum SlotError {
252    /// Data too large for inline storage
253    DataTooLarge {
254        /// Attempted inline payload size in bytes.
255        size: usize,
256        /// Maximum inline payload size in bytes.
257        max: usize,
258    },
259    /// Invalid slot marker byte
260    InvalidMarker {
261        /// Marker byte found in the slot.
262        found: u8,
263    },
264}
265
266impl fmt::Display for SlotError {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        match self {
269            Self::DataTooLarge { size, max } => {
270                write!(f, "Data size {} exceeds max inline size {}", size, max)
271            }
272            Self::InvalidMarker { found } => {
273                write!(f, "Invalid slot marker byte: 0x{:02X}", found)
274            }
275        }
276    }
277}
278
279impl std::error::Error for SlotError {}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[test]
286    fn test_slot_inline() {
287        let data = b"Hello, World!"; // 13 bytes
288        let slot = DxMachineSlot::inline_from_bytes(data).unwrap();
289
290        assert!(slot.is_inline());
291        assert!(!slot.is_heap());
292        assert_eq!(slot.inline_len(), 13);
293        assert_eq!(slot.inline_data(), data);
294        assert_eq!(slot.inline_str(), "Hello, World!");
295        assert_eq!(slot.size(), 13);
296    }
297
298    #[test]
299    fn test_slot_heap() {
300        let slot = DxMachineSlot::heap_reference(100, 500);
301
302        assert!(slot.is_heap());
303        assert!(!slot.is_inline());
304        assert_eq!(slot.heap_offset(), 100);
305        assert_eq!(slot.heap_length(), 500);
306        assert_eq!(slot.heap_ref(), (100, 500));
307        assert_eq!(slot.size(), 500);
308    }
309
310    #[test]
311    fn test_slot_max_inline() {
312        let data = b"12345678901234"; // 14 bytes (max)
313        let slot = DxMachineSlot::inline_from_bytes(data).unwrap();
314
315        assert!(slot.is_inline());
316        assert_eq!(slot.inline_len(), 14);
317        assert_eq!(slot.inline_data(), data);
318    }
319
320    #[test]
321    fn test_slot_too_large() {
322        let data = b"123456789012345"; // 15 bytes (too large)
323        let result = DxMachineSlot::inline_from_bytes(data);
324
325        assert!(matches!(
326            result,
327            Err(SlotError::DataTooLarge { size: 15, max: 14 })
328        ));
329    }
330
331    #[test]
332    fn test_slot_write_inline() {
333        let mut slot = DxMachineSlot::new();
334        slot.write_inline(b"Test").unwrap();
335
336        assert!(slot.is_inline());
337        assert_eq!(slot.inline_str(), "Test");
338    }
339
340    #[test]
341    fn test_slot_write_heap() {
342        let mut slot = DxMachineSlot::new();
343        slot.write_heap(42, 1000);
344
345        assert!(slot.is_heap());
346        assert_eq!(slot.heap_offset(), 42);
347        assert_eq!(slot.heap_length(), 1000);
348    }
349
350    #[test]
351    fn test_slot_eq() {
352        let slot = DxMachineSlot::inline_from_bytes(b"test").unwrap();
353
354        assert!(slot.eq_inline_bytes(b"test"));
355        assert!(!slot.eq_inline_bytes(b"Test"));
356        assert!(!slot.eq_inline_bytes(b"testing"));
357
358        assert!(slot.eq_inline_str("test"));
359        assert!(!slot.eq_inline_str("Test"));
360    }
361
362    #[test]
363    fn test_slot_size() {
364        assert_eq!(std::mem::size_of::<DxMachineSlot>(), 16);
365    }
366}