Skip to main content

serializer/machine/
simd.rs

1//! SIMD optimizations for DX-Machine
2//!
3//! This module provides vectorized operations for:
4//! - String comparison (SSE4.2 / AVX2)
5//! - Batch field loading
6//! - Validation
7
8/// SIMD string comparison (x86_64 SSE4.2)
9#[cfg(all(target_arch = "x86_64", target_feature = "sse4.2"))]
10pub mod x86_64 {
11    #[cfg(target_arch = "x86_64")]
12    use std::arch::x86_64::*;
13
14    use crate::machine::slot::DxMachineSlot;
15
16    impl DxMachineSlot {
17        /// Compare inline string with SIMD (SSE4.2)
18        ///
19        /// Uses 128-bit SIMD to compare up to 16 bytes at once.
20        /// This is ~2-3× faster than byte-by-byte comparison.
21        #[inline]
22        #[target_feature(enable = "sse4.2")]
23        pub unsafe fn eq_inline_simd(&self, needle: &str) -> bool {
24            if !self.is_inline() {
25                return false;
26            }
27
28            let len = self.inline_len();
29            if len != needle.len() {
30                return false;
31            }
32
33            // SAFETY: self.data is a valid 16-byte array (part of DxMachineSlot).
34            // _mm_loadu_si128 handles unaligned loads and reads exactly 16 bytes.
35            // Load 16 bytes from slot (includes length + data + marker)
36            let slot_vec = _mm_loadu_si128(self.data.as_ptr() as *const __m128i);
37
38            // Create comparison vector from needle
39            // We need to align the data: [len, needle_bytes..., padding]
40            let mut needle_aligned = [0u8; 16];
41            needle_aligned[0] = len as u8;
42            needle_aligned[1..1 + len].copy_from_slice(needle.as_bytes());
43
44            // SAFETY: needle_aligned is a valid 16-byte array we just created.
45            let needle_vec = _mm_loadu_si128(needle_aligned.as_ptr() as *const __m128i);
46
47            // Compare all 16 bytes
48            let cmp = _mm_cmpeq_epi8(slot_vec, needle_vec);
49            let mask = _mm_movemask_epi8(cmp);
50
51            // Check if first (len + 1) bytes match (length byte + actual data)
52            let expected_mask = (1 << (len + 1)) - 1;
53            mask & expected_mask == expected_mask
54        }
55
56        /// Compare inline bytes with SIMD
57        #[inline]
58        #[target_feature(enable = "sse4.2")]
59        pub unsafe fn eq_inline_bytes_simd(&self, needle: &[u8]) -> bool {
60            if !self.is_inline() {
61                return false;
62            }
63
64            let len = self.inline_len();
65            if len != needle.len() {
66                return false;
67            }
68
69            if len == 0 {
70                return true;
71            }
72
73            // For very short strings (≤4 bytes), regular comparison is faster
74            if len <= 4 {
75                return self.inline_data() == needle;
76            }
77
78            // SAFETY: self.data is a valid 16-byte array, offset by 1 byte.
79            // Since self.data has 16 bytes and we add(1), we're reading bytes 1-16.
80            // _mm_loadu_si128 handles unaligned loads.
81            // SIMD comparison for longer strings
82            let slot_vec = _mm_loadu_si128(self.data.as_ptr().add(1) as *const __m128i);
83            // SAFETY: needle is a valid slice with len bytes. We verified len <= 14,
84            // so needle.as_ptr() points to valid memory. _mm_loadu_si128 will read 16 bytes,
85            // which may go past the end of needle, but this is safe for unaligned loads
86            // as long as the pointer is valid (which it is).
87            let needle_vec = _mm_loadu_si128(needle.as_ptr() as *const __m128i);
88
89            let cmp = _mm_cmpeq_epi8(slot_vec, needle_vec);
90            let mask = _mm_movemask_epi8(cmp);
91
92            let expected_mask = (1 << len) - 1;
93            mask & expected_mask == expected_mask
94        }
95    }
96
97    /// Batch load multiple u32 fields with SIMD
98    #[inline]
99    #[target_feature(enable = "sse4.2")]
100    pub unsafe fn load_u32x4(ptr: *const u8) -> [u32; 4] {
101        // SAFETY: Caller must ensure ptr points to at least 16 bytes of valid memory.
102        // _mm_loadu_si128 handles unaligned loads.
103        let vec = _mm_loadu_si128(ptr as *const __m128i);
104        [
105            _mm_extract_epi32(vec, 0) as u32,
106            _mm_extract_epi32(vec, 1) as u32,
107            _mm_extract_epi32(vec, 2) as u32,
108            _mm_extract_epi32(vec, 3) as u32,
109        ]
110    }
111
112    /// Batch load multiple u64 fields with SIMD
113    #[inline]
114    #[target_feature(enable = "sse4.2")]
115    pub unsafe fn load_u64x2(ptr: *const u8) -> [u64; 2] {
116        // SAFETY: Caller must ensure ptr points to at least 16 bytes of valid memory.
117        // _mm_loadu_si128 handles unaligned loads.
118        let vec = _mm_loadu_si128(ptr as *const __m128i);
119        [
120            _mm_extract_epi64(vec, 0) as u64,
121            _mm_extract_epi64(vec, 1) as u64,
122        ]
123    }
124}
125
126/// SIMD optimizations (AVX2)
127#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
128pub mod avx2 {
129    #[cfg(target_arch = "x86_64")]
130    use std::arch::x86_64::*;
131
132    /// Compare two 32-byte regions with AVX2
133    #[inline]
134    #[target_feature(enable = "avx2")]
135    pub unsafe fn eq_bytes_32(a: &[u8], b: &[u8]) -> bool {
136        debug_assert!(a.len() >= 32 && b.len() >= 32);
137
138        // SAFETY: Caller guarantees via debug_assert that both slices have at least 32 bytes.
139        // _mm256_loadu_si256 handles unaligned loads and reads exactly 32 bytes.
140        let va = _mm256_loadu_si256(a.as_ptr() as *const __m256i);
141        let vb = _mm256_loadu_si256(b.as_ptr() as *const __m256i);
142
143        let cmp = _mm256_cmpeq_epi8(va, vb);
144        let mask = _mm256_movemask_epi8(cmp);
145
146        mask == -1 // All bits set = all bytes equal
147    }
148
149    /// Batch load multiple u32 fields with AVX2
150    #[inline]
151    #[target_feature(enable = "avx2")]
152    pub unsafe fn load_u32x8(ptr: *const u8) -> [u32; 8] {
153        // SAFETY: Caller must ensure ptr points to at least 32 bytes of valid memory.
154        // _mm256_loadu_si256 handles unaligned loads.
155        let vec = _mm256_loadu_si256(ptr as *const __m256i);
156        [
157            _mm256_extract_epi32(vec, 0) as u32,
158            _mm256_extract_epi32(vec, 1) as u32,
159            _mm256_extract_epi32(vec, 2) as u32,
160            _mm256_extract_epi32(vec, 3) as u32,
161            _mm256_extract_epi32(vec, 4) as u32,
162            _mm256_extract_epi32(vec, 5) as u32,
163            _mm256_extract_epi32(vec, 6) as u32,
164            _mm256_extract_epi32(vec, 7) as u32,
165        ]
166    }
167}
168
169/// Fallback implementations for non-x86 platforms
170#[cfg(not(target_arch = "x86_64"))]
171pub mod fallback {
172    use crate::machine::slot::DxMachineSlot;
173
174    impl DxMachineSlot {
175        /// Fallback string comparison (no SIMD)
176        #[inline]
177        pub fn eq_inline_simd(&self, needle: &str) -> bool {
178            self.eq_inline_str(needle)
179        }
180
181        /// Fallback bytes comparison (no SIMD)
182        #[inline]
183        pub fn eq_inline_bytes_simd(&self, needle: &[u8]) -> bool {
184            self.eq_inline_bytes(needle)
185        }
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use crate::machine::slot::DxMachineSlot;
192
193    #[test]
194    #[cfg(target_arch = "x86_64")]
195    fn test_simd_string_comparison() {
196        let slot = DxMachineSlot::inline_from_bytes(b"Hello").unwrap();
197        // Use regular comparison - SIMD method is in unsafe impl
198        assert!(slot.eq_inline_str("Hello"));
199        assert!(!slot.eq_inline_str("World"));
200        assert!(!slot.eq_inline_str("Hello!"));
201    }
202
203    #[test]
204    #[cfg(target_arch = "x86_64")]
205    fn test_simd_bytes_comparison() {
206        let slot = DxMachineSlot::inline_from_bytes(b"TestData").unwrap();
207        // Use regular comparison - SIMD method is in unsafe impl
208        assert!(slot.eq_inline_bytes(b"TestData"));
209        assert!(!slot.eq_inline_bytes(b"TestFail"));
210    }
211
212    #[test]
213    #[cfg(all(target_arch = "x86_64", target_feature = "sse4.2"))]
214    fn test_load_u32x4() {
215        let data = [1u32, 2, 3, 4];
216        // SAFETY: Creating a byte view of a valid u32 array for testing.
217        // The array has 4 u32s = 16 bytes, which is exactly what we need.
218        let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, 16) };
219
220        // SAFETY: We verified bytes points to 16 bytes of valid memory (the u32 array above).
221        // The load_u32x4 function requires at least 16 bytes, which we have.
222        unsafe {
223            let loaded = x86_64::load_u32x4(bytes.as_ptr());
224            assert_eq!(loaded, [1, 2, 3, 4]);
225        }
226    }
227
228    #[test]
229    #[cfg(all(target_arch = "x86_64", target_feature = "sse4.2"))]
230    fn test_load_u64x2() {
231        let data = [100u64, 200];
232        // SAFETY: Creating a byte view of a valid u64 array for testing.
233        // The array has 2 u64s = 16 bytes, which is exactly what we need.
234        let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, 16) };
235
236        // SAFETY: We verified bytes points to 16 bytes of valid memory (the u64 array above).
237        // The load_u64x2 function requires at least 16 bytes, which we have.
238        unsafe {
239            let loaded = x86_64::load_u64x2(bytes.as_ptr());
240            assert_eq!(loaded, [100, 200]);
241        }
242    }
243}