Skip to main content

kevy_bytes/
find_crlf.rs

1//! SIMD-accelerated `\r\n` scanner — A6 + A7.
2//!
3//! Replaces the u64 SWAR loop that lived in `kevy-resp::request::find_crlf`.
4//! Three-tier runtime dispatch:
5//!
6//! - **x86_64 + AVX2** (runtime detected): 32-byte `_mm256_cmpeq_epi8` →
7//!   `_mm256_movemask_epi8` loop. 4× the SWAR throughput.
8//! - **aarch64 + NEON** (mandatory in the ARMv8 baseline, no detection):
9//!   16-byte `vceqq_u8` + reduction. 2× the SWAR throughput.
10//! - **Fallback**: the same u64 SWAR algorithm that shipped before, kept
11//!   for non-AVX2 x86 boxes (Sandy Bridge etc.) and any non-x86_64 /
12//!   non-aarch64 target.
13//!
14//! AVX2 detection caches its decision in an `AtomicI8` so the cpuid hit
15//! amortises across the process lifetime.
16
17/// Find `\r\n` at or after `start`, returning the index of `\r`. Returns
18/// `None` if absent or if fewer than two bytes remain.
19#[inline]
20pub fn find_crlf(buf: &[u8], start: usize) -> Option<usize> {
21    #[cfg(target_arch = "x86_64")]
22    {
23        if has_avx2() {
24            // SAFETY: gated on runtime AVX2 detection above; the
25            // implementation only uses 256-bit intrinsics on chunks
26            // fully inside `buf`.
27            return unsafe { find_crlf_avx2(buf, start) };
28        }
29    }
30    #[cfg(target_arch = "aarch64")]
31    {
32        // SAFETY: NEON is part of the ARMv8 baseline (target_arch =
33        // "aarch64" implies it's present on every runtime that can run
34        // this binary at all).
35        return unsafe { find_crlf_neon(buf, start) };
36    }
37    #[allow(unreachable_code)]
38    find_crlf_swar(buf, start)
39}
40
41#[cfg(target_arch = "x86_64")]
42fn has_avx2() -> bool {
43    use core::sync::atomic::{AtomicI8, Ordering};
44    static CACHED: AtomicI8 = AtomicI8::new(-1);
45    let c = CACHED.load(Ordering::Relaxed);
46    if c >= 0 {
47        return c == 1;
48    }
49    let detected = std::is_x86_feature_detected!("avx2");
50    CACHED.store(i8::from(detected), Ordering::Relaxed);
51    detected
52}
53
54#[cfg(target_arch = "x86_64")]
55#[target_feature(enable = "avx2")]
56unsafe fn find_crlf_avx2(buf: &[u8], start: usize) -> Option<usize> {
57    use core::arch::x86_64::{
58        __m256i, _mm256_cmpeq_epi8, _mm256_loadu_si256, _mm256_movemask_epi8, _mm256_set1_epi8,
59    };
60
61    let n = buf.len();
62    if start + 1 >= n {
63        return None;
64    }
65    let mut i = start;
66    // Pure register op (safe inside #[target_feature(enable = "avx2")]).
67    let cr = _mm256_set1_epi8(0x0D);
68    // We need `i + 32 <= n` so the 32-byte load is in-bounds AND `i + 32 + 1
69    // <= n` so a CR at position 31 of the chunk can be confirmed by reading
70    // [pos + 1]. That collapses to `i + 32 < n`.
71    while i + 32 < n {
72        // SAFETY: `i + 32 < n` ⇒ bytes [i, i+31] are inside `buf`; the
73        // ptr is just past the buffer at most when this loop exits.
74        let chunk =
75            unsafe { _mm256_loadu_si256(buf.as_ptr().add(i) as *const __m256i) };
76        // Pure register ops on already-loaded vectors.
77        let mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(chunk, cr)) as u32;
78        if mask != 0 {
79            let bit = mask.trailing_zeros() as usize;
80            let pos = i + bit;
81            // `pos < i + 32 <= n - 1`, so `pos + 1 < n` is safe to index.
82            if buf[pos + 1] == b'\n' {
83                return Some(pos);
84            }
85            // Lone CR — resume scanning from the byte after it.
86            i = pos + 1;
87            continue;
88        }
89        i += 32;
90    }
91    // Tail: hand off to the SWAR scanner for the < 32-byte remainder.
92    find_crlf_swar(buf, i)
93}
94
95#[cfg(target_arch = "aarch64")]
96#[target_feature(enable = "neon")]
97unsafe fn find_crlf_neon(buf: &[u8], start: usize) -> Option<usize> {
98    use core::arch::aarch64::{vceqq_u8, vdupq_n_u8, vld1q_u8, vmaxvq_u8};
99
100    let n = buf.len();
101    if start + 1 >= n {
102        return None;
103    }
104    let mut i = start;
105    // Pure register op (safe inside #[target_feature(enable = "neon")]).
106    let cr = vdupq_n_u8(0x0D);
107    // Need `i + 16 < n` so the 16-byte load is in-bounds AND a CR at
108    // chunk byte 15 can be confirmed by reading [pos + 1] = [i + 16].
109    while i + 16 < n {
110        // SAFETY: `i + 16 < n` ⇒ bytes [i, i+15] are inside `buf`.
111        let chunk = unsafe { vld1q_u8(buf.as_ptr().add(i)) };
112        // Pure register ops on already-loaded vectors.
113        let eq = vceqq_u8(chunk, cr);
114        let any = vmaxvq_u8(eq);
115        if any != 0 {
116            // At least one CR in the chunk. Scalar-scan the 16 bytes to
117            // find the first one — a hit is very rare in typical RESP
118            // bulk-string scans (one CR per ~30+ byte arg), so this cold
119            // path doesn't need a vectorised reduction.
120            for j in 0..16 {
121                if buf[i + j] == b'\r' {
122                    let pos = i + j;
123                    // `pos < i + 16 <= n - 1` ⇒ `pos + 1 < n` safe.
124                    if buf[pos + 1] == b'\n' {
125                        return Some(pos);
126                    }
127                    // Lone CR — delegate to SWAR scanner from byte
128                    // after it (handles "the rest of this chunk + tail"
129                    // uniformly).
130                    return find_crlf_swar(buf, pos + 1);
131                }
132            }
133        }
134        i += 16;
135    }
136    find_crlf_swar(buf, i)
137}
138
139/// Portable u64 SWAR fallback — the same algorithm that shipped in
140/// `kevy-resp::request::find_crlf` before A6/A7.
141pub(crate) fn find_crlf_swar(buf: &[u8], start: usize) -> Option<usize> {
142    const CR_BCAST: u64 = 0x0D0D_0D0D_0D0D_0D0D_u64;
143    const ONES: u64 = 0x0101_0101_0101_0101_u64;
144    const HIGH: u64 = 0x8080_8080_8080_8080_u64;
145
146    let n = buf.len();
147    let mut i = start;
148    if i + 1 >= n {
149        return None;
150    }
151    while i + 8 < n {
152        let word = u64::from_le_bytes(buf[i..i + 8].try_into().expect("8 bytes"));
153        let x = word ^ CR_BCAST;
154        let zeroed = x.wrapping_sub(ONES) & !x & HIGH;
155        if zeroed != 0 {
156            let bit_idx = zeroed.trailing_zeros();
157            let pos = i + (bit_idx / 8) as usize;
158            if buf[pos + 1] == b'\n' {
159                return Some(pos);
160            }
161            i = pos + 1;
162            continue;
163        }
164        i += 8;
165    }
166    while i + 1 < n {
167        if buf[i] == b'\r' && buf[i + 1] == b'\n' {
168            return Some(i);
169        }
170        i += 1;
171    }
172    None
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    /// Cross-tier oracle: SWAR is the reference. Every implementation must
180    /// match it for every input shape.
181    fn assert_matches_swar(buf: &[u8]) {
182        for start in 0..=buf.len() {
183            assert_eq!(
184                find_crlf(buf, start),
185                find_crlf_swar(buf, start),
186                "mismatch at buf={buf:?} start={start}",
187            );
188        }
189    }
190
191    #[test]
192    fn empty_and_short_buffers() {
193        for buf in &[b"" as &[u8], b"a", b"\r", b"\n", b"ab"] {
194            assert_matches_swar(buf);
195        }
196    }
197
198    #[test]
199    fn crlf_at_every_offset() {
200        // RESP frames put CRLFs at non-trivial offsets — exercise each.
201        for off in 0..=80 {
202            let mut buf = vec![b'X'; off + 2];
203            buf[off] = b'\r';
204            buf[off + 1] = b'\n';
205            assert_eq!(find_crlf(&buf, 0), Some(off), "off={off}");
206        }
207    }
208
209    #[test]
210    fn lone_cr_does_not_terminate() {
211        // CR without LF must be skipped; the scanner must continue past it.
212        let mut buf = vec![b'X'; 50];
213        buf[10] = b'\r';
214        // No CRLF in the buffer at all.
215        assert_eq!(find_crlf(&buf, 0), None);
216        // Plant a real CRLF after the lone CR — must find it.
217        buf[30] = b'\r';
218        buf[31] = b'\n';
219        assert_eq!(find_crlf(&buf, 0), Some(30));
220    }
221
222    #[test]
223    fn multiple_crs_in_a_row() {
224        let buf = b"X\r\r\r\nY";
225        assert_eq!(find_crlf(buf, 0), Some(3));
226    }
227
228    #[test]
229    fn start_past_crlf_finds_next() {
230        let buf = b"AAA\r\nBBB\r\nCCC";
231        assert_eq!(find_crlf(buf, 0), Some(3));
232        assert_eq!(find_crlf(buf, 4), Some(8));
233        assert_eq!(find_crlf(buf, 9), None);
234    }
235
236    #[test]
237    fn cross_tier_oracle_random_shapes() {
238        // Mix of buffer sizes that span the SIMD chunk boundary (16-byte
239        // NEON, 32-byte AVX2): hit pre-chunk, in-chunk, in-tail.
240        let shapes: &[&[u8]] = &[
241            b"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
242            b"*1\r\n$4\r\nPING\r\n",
243            b"PING\r\n",
244            b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\n", // CRLF right after a full AVX2 chunk
245            b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r\n", // CRLF one byte after
246            b"\r\nXX\r\nXXXXXX\r\n", // multiple CRLFs, hit each in turn
247            b"XXXXXXXXXXXXXXXX\rXXXXXXXXXXXXXXXX\r\n", // lone CR in chunk N, real CRLF in chunk N+1
248            b"only-text-no-newline-at-all-just-bytes-here-XXXX",
249        ];
250        for buf in shapes {
251            assert_matches_swar(buf);
252        }
253    }
254
255    #[test]
256    fn returns_none_when_only_one_byte_after_start() {
257        let buf = b"AAAAA";
258        assert_eq!(find_crlf(buf, 4), None);
259        assert_eq!(find_crlf(buf, 5), None);
260    }
261}