fionn_diff/
simd_compare.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! SIMD-accelerated comparison utilities for JSON diff operations.
3//!
4//! Provides fast byte-level comparison for detecting differences between
5//! JSON values without full parsing.
6
7#[cfg(target_arch = "x86_64")]
8use std::arch::x86_64::{
9    _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm256_cmpeq_epi8, _mm256_loadu_si256,
10    _mm256_movemask_epi8,
11};
12
13#[cfg(target_arch = "aarch64")]
14use std::arch::aarch64::{uint8x16_t, vceqq_u8, vld1q_u8, vminvq_u8};
15
16use std::sync::OnceLock;
17
18/// Cached SIMD feature detection.
19#[cfg(target_arch = "x86_64")]
20static HAS_SSE2: OnceLock<bool> = OnceLock::new();
21
22#[cfg(target_arch = "x86_64")]
23static HAS_AVX2: OnceLock<bool> = OnceLock::new();
24
25#[cfg(target_arch = "x86_64")]
26#[inline]
27fn has_sse2() -> bool {
28    *HAS_SSE2.get_or_init(|| is_x86_feature_detected!("sse2"))
29}
30
31#[cfg(target_arch = "x86_64")]
32#[inline]
33fn has_avx2() -> bool {
34    *HAS_AVX2.get_or_init(|| is_x86_feature_detected!("avx2"))
35}
36
37/// SIMD-accelerated byte slice equality check.
38///
39/// Returns `true` if both slices are equal, `false` otherwise.
40/// Uses SIMD to compare 16-32 bytes at a time.
41#[inline]
42#[must_use]
43pub fn simd_bytes_equal(a: &[u8], b: &[u8]) -> bool {
44    if a.len() != b.len() {
45        return false;
46    }
47
48    if a.is_empty() {
49        return true;
50    }
51
52    #[cfg(target_arch = "x86_64")]
53    {
54        if a.len() >= 32 && has_avx2() {
55            return unsafe { simd_bytes_equal_avx2(a, b) };
56        }
57        if a.len() >= 16 && has_sse2() {
58            return unsafe { simd_bytes_equal_sse2(a, b) };
59        }
60    }
61
62    #[cfg(target_arch = "aarch64")]
63    {
64        if a.len() >= 16 {
65            return unsafe { simd_bytes_equal_neon(a, b) };
66        }
67    }
68
69    // Scalar fallback
70    a == b
71}
72
73/// Find the first position where two byte slices differ.
74///
75/// Returns `None` if the slices are equal, or `Some(index)` of the first difference.
76/// If slices have different lengths, returns the length of the shorter slice
77/// if the common prefix is equal.
78#[inline]
79#[must_use]
80pub fn simd_find_first_difference(a: &[u8], b: &[u8]) -> Option<usize> {
81    let min_len = a.len().min(b.len());
82
83    if min_len == 0 {
84        return if a.len() == b.len() { None } else { Some(0) };
85    }
86
87    #[cfg(target_arch = "x86_64")]
88    {
89        if min_len >= 32 && has_avx2() {
90            let diff = unsafe { find_first_difference_avx2(a, b, min_len) };
91            if let Some(pos) = diff {
92                return Some(pos);
93            }
94            // Check if lengths differ
95            return if a.len() == b.len() {
96                None
97            } else {
98                Some(min_len)
99            };
100        }
101        if min_len >= 16 && has_sse2() {
102            let diff = unsafe { find_first_difference_sse2(a, b, min_len) };
103            if let Some(pos) = diff {
104                return Some(pos);
105            }
106            return if a.len() == b.len() {
107                None
108            } else {
109                Some(min_len)
110            };
111        }
112    }
113
114    #[cfg(target_arch = "aarch64")]
115    {
116        if min_len >= 16 {
117            let diff = unsafe { find_first_difference_neon(a, b, min_len) };
118            if let Some(pos) = diff {
119                return Some(pos);
120            }
121            return if a.len() == b.len() {
122                None
123            } else {
124                Some(min_len)
125            };
126        }
127    }
128
129    // Scalar fallback
130    for (i, (&byte_a, &byte_b)) in a.iter().zip(b.iter()).enumerate() {
131        if byte_a != byte_b {
132            return Some(i);
133        }
134    }
135
136    if a.len() == b.len() {
137        None
138    } else {
139        Some(min_len)
140    }
141}
142
143/// SSE2 implementation of byte equality.
144#[cfg(target_arch = "x86_64")]
145#[target_feature(enable = "sse2")]
146unsafe fn simd_bytes_equal_sse2(a: &[u8], b: &[u8]) -> bool {
147    unsafe {
148        let len = a.len();
149        let mut i = 0;
150
151        // Process 16 bytes at a time
152        while i + 16 <= len {
153            let chunk_a = _mm_loadu_si128(a[i..].as_ptr().cast());
154            let chunk_b = _mm_loadu_si128(b[i..].as_ptr().cast());
155            let cmp = _mm_cmpeq_epi8(chunk_a, chunk_b);
156            let mask = _mm_movemask_epi8(cmp);
157
158            // All 16 bytes must be equal (mask = 0xFFFF)
159            if mask != 0xFFFF {
160                return false;
161            }
162
163            i += 16;
164        }
165
166        // Check remaining bytes
167        a[i..] == b[i..]
168    }
169}
170
171/// AVX2 implementation of byte equality.
172#[cfg(target_arch = "x86_64")]
173#[target_feature(enable = "avx2")]
174unsafe fn simd_bytes_equal_avx2(a: &[u8], b: &[u8]) -> bool {
175    unsafe {
176        let len = a.len();
177        let mut i = 0;
178
179        // Process 32 bytes at a time
180        while i + 32 <= len {
181            let chunk_a = _mm256_loadu_si256(a[i..].as_ptr().cast());
182            let chunk_b = _mm256_loadu_si256(b[i..].as_ptr().cast());
183            let cmp = _mm256_cmpeq_epi8(chunk_a, chunk_b);
184            let mask = _mm256_movemask_epi8(cmp);
185
186            // All 32 bytes must be equal (mask = -1 as i32, or 0xFFFFFFFF)
187            if mask != -1 {
188                return false;
189            }
190
191            i += 32;
192        }
193
194        // Handle remaining with SSE2 or scalar
195        if i + 16 <= len && has_sse2() {
196            let chunk_a = _mm_loadu_si128(a[i..].as_ptr().cast());
197            let chunk_b = _mm_loadu_si128(b[i..].as_ptr().cast());
198            let cmp = _mm_cmpeq_epi8(chunk_a, chunk_b);
199            let mask = _mm_movemask_epi8(cmp);
200
201            if mask != 0xFFFF {
202                return false;
203            }
204            i += 16;
205        }
206
207        // Check remaining bytes
208        a[i..] == b[i..]
209    }
210}
211
212/// NEON implementation of byte equality.
213#[cfg(target_arch = "aarch64")]
214unsafe fn simd_bytes_equal_neon(a: &[u8], b: &[u8]) -> bool {
215    unsafe {
216        let len = a.len();
217        let mut i = 0;
218
219        // Process 16 bytes at a time
220        while i + 16 <= len {
221            let chunk_a = vld1q_u8(a[i..].as_ptr());
222            let chunk_b = vld1q_u8(b[i..].as_ptr());
223            let cmp = vceqq_u8(chunk_a, chunk_b);
224
225            // Check if all bytes are equal (all comparison results are 0xFF)
226            let min_val = vminvq_u8(cmp);
227            if min_val != 0xFF {
228                return false;
229            }
230
231            i += 16;
232        }
233
234        // Check remaining bytes
235        a[i..] == b[i..]
236    }
237}
238
239/// SSE2 implementation of finding first difference.
240#[cfg(target_arch = "x86_64")]
241#[target_feature(enable = "sse2")]
242unsafe fn find_first_difference_sse2(a: &[u8], b: &[u8], min_len: usize) -> Option<usize> {
243    unsafe {
244        let mut i = 0;
245
246        while i + 16 <= min_len {
247            let chunk_a = _mm_loadu_si128(a[i..].as_ptr().cast());
248            let chunk_b = _mm_loadu_si128(b[i..].as_ptr().cast());
249            let cmp = _mm_cmpeq_epi8(chunk_a, chunk_b);
250            let mask = _mm_movemask_epi8(cmp);
251
252            if mask != 0xFFFF {
253                // Found a difference - find which byte
254                let diff_mask = !mask & 0xFFFF;
255                let offset = diff_mask.trailing_zeros() as usize;
256                return Some(i + offset);
257            }
258
259            i += 16;
260        }
261
262        // Check remaining bytes
263        for j in i..min_len {
264            if a[j] != b[j] {
265                return Some(j);
266            }
267        }
268
269        None
270    }
271}
272
273/// AVX2 implementation of finding first difference.
274#[cfg(target_arch = "x86_64")]
275#[target_feature(enable = "avx2")]
276unsafe fn find_first_difference_avx2(a: &[u8], b: &[u8], min_len: usize) -> Option<usize> {
277    unsafe {
278        let mut i = 0;
279
280        while i + 32 <= min_len {
281            let chunk_a = _mm256_loadu_si256(a[i..].as_ptr().cast());
282            let chunk_b = _mm256_loadu_si256(b[i..].as_ptr().cast());
283            let cmp = _mm256_cmpeq_epi8(chunk_a, chunk_b);
284            let mask = _mm256_movemask_epi8(cmp);
285
286            if mask != -1 {
287                // Found a difference - find which byte
288                let diff_mask = (!mask).cast_unsigned();
289                let offset = diff_mask.trailing_zeros() as usize;
290                return Some(i + offset);
291            }
292
293            i += 32;
294        }
295
296        // Handle remaining with SSE2 or scalar
297        if i + 16 <= min_len && has_sse2() {
298            let chunk_a = _mm_loadu_si128(a[i..].as_ptr().cast());
299            let chunk_b = _mm_loadu_si128(b[i..].as_ptr().cast());
300            let cmp = _mm_cmpeq_epi8(chunk_a, chunk_b);
301            let mask = _mm_movemask_epi8(cmp);
302
303            if mask != 0xFFFF {
304                let diff_mask = !mask & 0xFFFF;
305                let offset = diff_mask.trailing_zeros() as usize;
306                return Some(i + offset);
307            }
308            i += 16;
309        }
310
311        // Check remaining bytes
312        for j in i..min_len {
313            if a[j] != b[j] {
314                return Some(j);
315            }
316        }
317
318        None
319    }
320}
321
322/// NEON implementation of finding first difference.
323#[cfg(target_arch = "aarch64")]
324unsafe fn find_first_difference_neon(a: &[u8], b: &[u8], min_len: usize) -> Option<usize> {
325    unsafe {
326        let mut i = 0;
327
328        while i + 16 <= min_len {
329            let chunk_a = vld1q_u8(a[i..].as_ptr());
330            let chunk_b = vld1q_u8(b[i..].as_ptr());
331            let cmp = vceqq_u8(chunk_a, chunk_b);
332
333            // Check if all bytes are equal
334            let min_val = vminvq_u8(cmp);
335            if min_val != 0xFF {
336                // Found a difference - find which byte
337                let arr: [u8; 16] = std::mem::transmute(cmp);
338                for (offset, &val) in arr.iter().enumerate() {
339                    if val != 0xFF {
340                        return Some(i + offset);
341                    }
342                }
343            }
344
345            i += 16;
346        }
347
348        // Check remaining bytes
349        for j in i..min_len {
350            if a[j] != b[j] {
351                return Some(j);
352            }
353        }
354
355        None
356    }
357}
358
359/// Compare two JSON string values for equality.
360///
361/// This is optimized for JSON strings which are typically short.
362/// Uses SIMD for strings >= 16 bytes.
363#[inline]
364#[must_use]
365pub fn json_strings_equal(a: &str, b: &str) -> bool {
366    simd_bytes_equal(a.as_bytes(), b.as_bytes())
367}
368
369/// Compare two JSON number representations for equality.
370///
371/// Note: This does byte comparison, so "1.0" != "1" even though
372/// they represent the same value. For semantic equality, parse first.
373#[inline]
374#[must_use]
375pub fn json_numbers_equal(a: &str, b: &str) -> bool {
376    // Numbers are typically short, use direct comparison
377    a == b
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    // =========================================================================
385    // simd_bytes_equal Tests
386    // =========================================================================
387
388    #[test]
389    fn test_simd_bytes_equal_same() {
390        let a = b"hello world";
391        let b = b"hello world";
392        assert!(simd_bytes_equal(a, b));
393    }
394
395    #[test]
396    fn test_simd_bytes_equal_different() {
397        let a = b"hello world";
398        let b = b"hello worle";
399        assert!(!simd_bytes_equal(a, b));
400    }
401
402    #[test]
403    fn test_simd_bytes_equal_different_length() {
404        let a = b"hello";
405        let b = b"hello world";
406        assert!(!simd_bytes_equal(a, b));
407    }
408
409    #[test]
410    fn test_simd_bytes_equal_empty() {
411        let a: &[u8] = b"";
412        let b: &[u8] = b"";
413        assert!(simd_bytes_equal(a, b));
414    }
415
416    #[test]
417    fn test_simd_bytes_equal_long() {
418        // Test with data longer than 32 bytes to trigger AVX2
419        let a = "a".repeat(100);
420        let b = "a".repeat(100);
421        assert!(simd_bytes_equal(a.as_bytes(), b.as_bytes()));
422
423        let mut c = "a".repeat(100);
424        c.replace_range(50..51, "b");
425        assert!(!simd_bytes_equal(a.as_bytes(), c.as_bytes()));
426    }
427
428    #[test]
429    fn test_simd_bytes_equal_exactly_16() {
430        // Test SSE2 boundary (16 bytes)
431        let a = b"1234567890123456";
432        let b = b"1234567890123456";
433        assert!(simd_bytes_equal(a, b));
434
435        let c = b"1234567890123457";
436        assert!(!simd_bytes_equal(a, c));
437    }
438
439    #[test]
440    fn test_simd_bytes_equal_exactly_32() {
441        // Test AVX2 boundary (32 bytes)
442        let a = b"12345678901234567890123456789012";
443        let b = b"12345678901234567890123456789012";
444        assert!(simd_bytes_equal(a, b));
445
446        let c = b"12345678901234567890123456789013";
447        assert!(!simd_bytes_equal(a, c));
448    }
449
450    #[test]
451    fn test_simd_bytes_equal_17_bytes() {
452        // Test between SSE2 boundaries (16 < 17 < 32)
453        let a = b"12345678901234567";
454        let b = b"12345678901234567";
455        assert!(simd_bytes_equal(a, b));
456
457        let c = b"12345678901234568";
458        assert!(!simd_bytes_equal(a, c));
459    }
460
461    #[test]
462    fn test_simd_bytes_equal_33_bytes() {
463        // Test between AVX2 boundaries (32 < 33 < 48)
464        let a = b"123456789012345678901234567890123";
465        let b = b"123456789012345678901234567890123";
466        assert!(simd_bytes_equal(a, b));
467
468        let c = b"123456789012345678901234567890124";
469        assert!(!simd_bytes_equal(a, c));
470    }
471
472    #[test]
473    fn test_simd_bytes_equal_48_bytes() {
474        // Test 48 bytes (32 + 16)
475        let a = "x".repeat(48);
476        let b = "x".repeat(48);
477        assert!(simd_bytes_equal(a.as_bytes(), b.as_bytes()));
478
479        let mut c = "x".repeat(48);
480        c.replace_range(47..48, "y");
481        assert!(!simd_bytes_equal(a.as_bytes(), c.as_bytes()));
482    }
483
484    #[test]
485    fn test_simd_bytes_equal_64_bytes() {
486        // Test 64 bytes (2 x 32)
487        let a = "z".repeat(64);
488        let b = "z".repeat(64);
489        assert!(simd_bytes_equal(a.as_bytes(), b.as_bytes()));
490    }
491
492    #[test]
493    fn test_simd_bytes_equal_difference_in_first_chunk() {
494        let a = "x".repeat(64);
495        let mut b = "x".repeat(64);
496        b.replace_range(5..6, "y");
497        assert!(!simd_bytes_equal(a.as_bytes(), b.as_bytes()));
498    }
499
500    #[test]
501    fn test_simd_bytes_equal_difference_in_second_chunk() {
502        let a = "x".repeat(64);
503        let mut b = "x".repeat(64);
504        b.replace_range(35..36, "y");
505        assert!(!simd_bytes_equal(a.as_bytes(), b.as_bytes()));
506    }
507
508    #[test]
509    fn test_simd_bytes_equal_difference_in_remainder() {
510        let a = "x".repeat(50);
511        let mut b = "x".repeat(50);
512        b.replace_range(49..50, "y");
513        assert!(!simd_bytes_equal(a.as_bytes(), b.as_bytes()));
514    }
515
516    // =========================================================================
517    // simd_find_first_difference Tests
518    // =========================================================================
519
520    #[test]
521    fn test_find_first_difference_none() {
522        let a = b"hello world";
523        let b = b"hello world";
524        assert_eq!(simd_find_first_difference(a, b), None);
525    }
526
527    #[test]
528    fn test_find_first_difference_at_start() {
529        let a = b"hello";
530        let b = b"jello";
531        assert_eq!(simd_find_first_difference(a, b), Some(0));
532    }
533
534    #[test]
535    fn test_find_first_difference_at_end() {
536        let a = b"hello";
537        let b = b"hellp";
538        assert_eq!(simd_find_first_difference(a, b), Some(4));
539    }
540
541    #[test]
542    fn test_find_first_difference_length() {
543        let a = b"hello";
544        let b = b"hello world";
545        assert_eq!(simd_find_first_difference(a, b), Some(5));
546    }
547
548    #[test]
549    fn test_find_first_difference_empty() {
550        let a: &[u8] = b"";
551        let b: &[u8] = b"";
552        assert_eq!(simd_find_first_difference(a, b), None);
553
554        let c = b"hello";
555        assert_eq!(simd_find_first_difference(a, c), Some(0));
556    }
557
558    #[test]
559    fn test_find_first_difference_long() {
560        let a = "a".repeat(100);
561        let b = "a".repeat(100);
562        assert_eq!(simd_find_first_difference(a.as_bytes(), b.as_bytes()), None);
563
564        let mut c = "a".repeat(100);
565        c.replace_range(75..76, "b");
566        assert_eq!(
567            simd_find_first_difference(a.as_bytes(), c.as_bytes()),
568            Some(75)
569        );
570    }
571
572    #[test]
573    fn test_find_first_difference_16_bytes() {
574        let a = b"1234567890123456";
575        let b = b"1234567890123456";
576        assert_eq!(simd_find_first_difference(a, b), None);
577
578        let c = b"1234567890123457";
579        assert_eq!(simd_find_first_difference(a, c), Some(15));
580    }
581
582    #[test]
583    fn test_find_first_difference_32_bytes() {
584        let a = b"12345678901234567890123456789012";
585        let b = b"12345678901234567890123456789012";
586        assert_eq!(simd_find_first_difference(a, b), None);
587
588        let c = b"12345678901234567890123456789013";
589        assert_eq!(simd_find_first_difference(a, c), Some(31));
590    }
591
592    #[test]
593    fn test_find_first_difference_in_first_sse_chunk() {
594        // Difference in first 16 bytes
595        let a = "x".repeat(32);
596        let mut b = "x".repeat(32);
597        b.replace_range(3..4, "y");
598        assert_eq!(
599            simd_find_first_difference(a.as_bytes(), b.as_bytes()),
600            Some(3)
601        );
602    }
603
604    #[test]
605    fn test_find_first_difference_in_second_sse_chunk() {
606        // Difference in second 16 bytes (for SSE path)
607        let a = "x".repeat(32);
608        let mut b = "x".repeat(32);
609        b.replace_range(20..21, "y");
610        assert_eq!(
611            simd_find_first_difference(a.as_bytes(), b.as_bytes()),
612            Some(20)
613        );
614    }
615
616    #[test]
617    fn test_find_first_difference_in_first_avx_chunk() {
618        // Difference in first 32 bytes
619        let a = "x".repeat(64);
620        let mut b = "x".repeat(64);
621        b.replace_range(10..11, "y");
622        assert_eq!(
623            simd_find_first_difference(a.as_bytes(), b.as_bytes()),
624            Some(10)
625        );
626    }
627
628    #[test]
629    fn test_find_first_difference_in_second_avx_chunk() {
630        // Difference in second 32 bytes
631        let a = "x".repeat(64);
632        let mut b = "x".repeat(64);
633        b.replace_range(40..41, "y");
634        assert_eq!(
635            simd_find_first_difference(a.as_bytes(), b.as_bytes()),
636            Some(40)
637        );
638    }
639
640    #[test]
641    fn test_find_first_difference_in_sse_remainder_of_avx() {
642        // 48 bytes: 32 (AVX) + 16 (SSE remainder)
643        let a = "x".repeat(48);
644        let mut b = "x".repeat(48);
645        b.replace_range(35..36, "y");
646        assert_eq!(
647            simd_find_first_difference(a.as_bytes(), b.as_bytes()),
648            Some(35)
649        );
650    }
651
652    #[test]
653    fn test_find_first_difference_in_scalar_remainder() {
654        // 50 bytes: 32 (AVX) + 16 (SSE) + 2 (scalar)
655        let a = "x".repeat(50);
656        let mut b = "x".repeat(50);
657        b.replace_range(49..50, "y");
658        assert_eq!(
659            simd_find_first_difference(a.as_bytes(), b.as_bytes()),
660            Some(49)
661        );
662    }
663
664    #[test]
665    fn test_find_first_difference_different_lengths_same_prefix() {
666        let a = b"hello";
667        let b = b"hello world";
668        assert_eq!(simd_find_first_difference(a, b), Some(5));
669
670        // Longer a
671        let a = b"hello world";
672        let b = b"hello";
673        assert_eq!(simd_find_first_difference(a, b), Some(5));
674    }
675
676    #[test]
677    fn test_find_first_difference_long_equal_slices() {
678        // Very long equal slices (tests multiple SIMD iterations)
679        let a = "x".repeat(500);
680        let b = "x".repeat(500);
681        assert_eq!(simd_find_first_difference(a.as_bytes(), b.as_bytes()), None);
682    }
683
684    #[test]
685    fn test_find_first_difference_long_difference_at_end() {
686        let a = "x".repeat(500);
687        let mut b = "x".repeat(500);
688        b.replace_range(499..500, "y");
689        assert_eq!(
690            simd_find_first_difference(a.as_bytes(), b.as_bytes()),
691            Some(499)
692        );
693    }
694
695    // =========================================================================
696    // json_strings_equal Tests
697    // =========================================================================
698
699    #[test]
700    fn test_json_strings_equal() {
701        assert!(json_strings_equal("hello", "hello"));
702        assert!(!json_strings_equal("hello", "world"));
703        assert!(json_strings_equal("", ""));
704    }
705
706    #[test]
707    fn test_json_strings_equal_long() {
708        let s = "a".repeat(100);
709        assert!(json_strings_equal(&s, &s));
710    }
711
712    #[test]
713    fn test_json_strings_equal_unicode() {
714        assert!(json_strings_equal("日本語", "日本語"));
715        assert!(!json_strings_equal("日本語", "中文"));
716    }
717
718    // =========================================================================
719    // json_numbers_equal Tests
720    // =========================================================================
721
722    #[test]
723    fn test_json_numbers_equal() {
724        assert!(json_numbers_equal("42", "42"));
725        assert!(json_numbers_equal("3.14", "3.14"));
726        // Note: These are semantically equal but byte-different
727        assert!(!json_numbers_equal("1.0", "1"));
728    }
729
730    #[test]
731    fn test_json_numbers_equal_negative() {
732        assert!(json_numbers_equal("-42", "-42"));
733        assert!(!json_numbers_equal("-42", "42"));
734    }
735
736    #[test]
737    fn test_json_numbers_equal_exponent() {
738        assert!(json_numbers_equal("1e10", "1e10"));
739        assert!(!json_numbers_equal("1e10", "1E10")); // Case sensitive
740    }
741
742    // =========================================================================
743    // Feature Detection Tests
744    // =========================================================================
745
746    #[cfg(target_arch = "x86_64")]
747    #[test]
748    fn test_has_sse2() {
749        // SSE2 is baseline for x86_64
750        assert!(has_sse2());
751    }
752
753    #[cfg(target_arch = "x86_64")]
754    #[test]
755    fn test_has_avx2() {
756        // Just check it doesn't panic
757        let _ = has_avx2();
758    }
759
760    // =========================================================================
761    // Scalar Fallback Tests (small inputs)
762    // =========================================================================
763
764    #[test]
765    fn test_simd_bytes_equal_scalar_fallback() {
766        // Very short inputs use scalar path
767        let a = b"hi";
768        let b = b"hi";
769        assert!(simd_bytes_equal(a, b));
770
771        let c = b"ho";
772        assert!(!simd_bytes_equal(a, c));
773    }
774
775    #[test]
776    fn test_find_first_difference_scalar_fallback() {
777        // Very short inputs use scalar path
778        let a = b"abc";
779        let b = b"abc";
780        assert_eq!(simd_find_first_difference(a, b), None);
781
782        let c = b"abd";
783        assert_eq!(simd_find_first_difference(a, c), Some(2));
784    }
785
786    #[test]
787    fn test_simd_bytes_equal_single_byte() {
788        assert!(simd_bytes_equal(b"a", b"a"));
789        assert!(!simd_bytes_equal(b"a", b"b"));
790    }
791
792    #[test]
793    fn test_find_first_difference_single_byte() {
794        assert_eq!(simd_find_first_difference(b"a", b"a"), None);
795        assert_eq!(simd_find_first_difference(b"a", b"b"), Some(0));
796    }
797}