1#[cfg(target_arch = "x86_64")]
8use core::arch::x86_64::*;
9
10use crate::{DelimiterResult, classify_byte};
11
12#[target_feature(enable = "sse4.2")]
20#[cfg(target_arch = "x86_64")]
21pub unsafe fn find_delimiters(haystack: &[u8]) -> DelimiterResult {
22 let len = haystack.len();
23 let ptr = haystack.as_ptr();
24 let mut offset = 0;
25
26 unsafe {
28 let lt = _mm_set1_epi8(b'<' as i8);
35 let gt = _mm_set1_epi8(b'>' as i8);
36 let amp = _mm_set1_epi8(b'&' as i8);
37 let quot = _mm_set1_epi8(b'"' as i8);
38 let apos = _mm_set1_epi8(b'\'' as i8);
39 let eq = _mm_set1_epi8(b'=' as i8);
40 let slash = _mm_set1_epi8(b'/' as i8);
41
42 while offset + 16 <= len {
43 let chunk = _mm_loadu_si128(ptr.add(offset) as *const __m128i);
45
46 let m = _mm_or_si128(
49 _mm_or_si128(
50 _mm_or_si128(_mm_cmpeq_epi8(chunk, lt), _mm_cmpeq_epi8(chunk, gt)),
51 _mm_or_si128(_mm_cmpeq_epi8(chunk, amp), _mm_cmpeq_epi8(chunk, quot)),
52 ),
53 _mm_or_si128(
54 _mm_or_si128(_mm_cmpeq_epi8(chunk, apos), _mm_cmpeq_epi8(chunk, eq)),
55 _mm_cmpeq_epi8(chunk, slash),
56 ),
57 );
58 let mask = _mm_movemask_epi8(m) as u16;
59 if mask != 0 {
60 let pos = offset + mask.trailing_zeros() as usize;
61 return DelimiterResult::Found {
62 pos,
63 byte: *ptr.add(pos),
64 };
65 }
66 offset += 16;
67 }
68 }
69
70 crate::scalar::find_delimiters_safe(&haystack[offset..]).offset_by(offset)
72}
73
74#[target_feature(enable = "sse4.2")]
80#[cfg(target_arch = "x86_64")]
81pub unsafe fn classify_bytes(input: &[u8]) -> Vec<u8> {
82 let len = input.len();
83 let mut result = Vec::with_capacity(len);
84 let ptr = input.as_ptr();
85 let out_ptr: *mut u8 = result.as_mut_ptr();
86 let mut offset = 0;
87
88 unsafe {
91 while offset + 16 <= len {
92 let chunk = _mm_loadu_si128(ptr.add(offset) as *const __m128i);
93
94 let space = _mm_set1_epi8(b' ' as i8);
96 let tab = _mm_set1_epi8(b'\t' as i8);
97 let nl = _mm_set1_epi8(b'\n' as i8);
98 let cr = _mm_set1_epi8(b'\r' as i8);
99
100 let ws_mask = _mm_or_si128(
102 _mm_or_si128(_mm_cmpeq_epi8(chunk, space), _mm_cmpeq_epi8(chunk, tab)),
103 _mm_or_si128(_mm_cmpeq_epi8(chunk, nl), _mm_cmpeq_epi8(chunk, cr)),
104 );
105
106 let or_mask = _mm_set1_epi8(0x20);
108 let lower = _mm_or_si128(chunk, or_mask); let a_val = _mm_set1_epi8(b'a' as i8);
110 let sub = _mm_sub_epi8(lower, a_val); let bound = _mm_set1_epi8(25); let clamped = _mm_min_epu8(sub, bound);
114 let alpha_mask = _mm_cmpeq_epi8(sub, clamped);
115
116 let zero = _mm_set1_epi8(b'0' as i8);
118 let sub_d = _mm_sub_epi8(chunk, zero);
119 let dbound = _mm_set1_epi8(9);
120 let dclamped = _mm_min_epu8(sub_d, dbound);
121 let digit_mask = _mm_cmpeq_epi8(sub_d, dclamped);
122
123 let lt = _mm_set1_epi8(b'<' as i8);
125 let gt = _mm_set1_epi8(b'>' as i8);
126 let amp = _mm_set1_epi8(b'&' as i8);
127 let quot = _mm_set1_epi8(b'"' as i8);
128 let apos = _mm_set1_epi8(b'\'' as i8);
129 let eq = _mm_set1_epi8(b'=' as i8);
130 let slash = _mm_set1_epi8(b'/' as i8);
131
132 let delim_mask = _mm_or_si128(
133 _mm_or_si128(
134 _mm_or_si128(_mm_cmpeq_epi8(chunk, lt), _mm_cmpeq_epi8(chunk, gt)),
135 _mm_or_si128(_mm_cmpeq_epi8(chunk, amp), _mm_cmpeq_epi8(chunk, quot)),
136 ),
137 _mm_or_si128(
138 _mm_or_si128(_mm_cmpeq_epi8(chunk, apos), _mm_cmpeq_epi8(chunk, eq)),
139 _mm_cmpeq_epi8(chunk, slash),
140 ),
141 );
142
143 let ws_class = _mm_and_si128(ws_mask, _mm_set1_epi8(crate::class::WHITESPACE as i8));
145 let al_class = _mm_and_si128(alpha_mask, _mm_set1_epi8(crate::class::ALPHA as i8));
146 let di_class = _mm_and_si128(digit_mask, _mm_set1_epi8(crate::class::DIGIT as i8));
147 let de_class = _mm_and_si128(delim_mask, _mm_set1_epi8(crate::class::DELIMITER as i8));
148
149 let combined = _mm_or_si128(
150 _mm_or_si128(ws_class, al_class),
151 _mm_or_si128(di_class, de_class),
152 );
153
154 _mm_storeu_si128(out_ptr.add(offset) as *mut __m128i, combined);
156 offset += 16;
157 }
158
159 while offset < len {
161 *out_ptr.add(offset) = classify_byte(*ptr.add(offset));
162 offset += 1;
163 }
164
165 result.set_len(len);
166 }
167
168 result
169}
170
171#[target_feature(enable = "sse4.2")]
177#[cfg(target_arch = "x86_64")]
178pub unsafe fn skip_whitespace(input: &[u8]) -> usize {
179 let len = input.len();
180 let ptr = input.as_ptr();
181 let mut offset = 0;
182
183 unsafe {
185 let space = _mm_set1_epi8(b' ' as i8);
190 let tab = _mm_set1_epi8(b'\t' as i8);
191 let nl = _mm_set1_epi8(b'\n' as i8);
192 let cr = _mm_set1_epi8(b'\r' as i8);
193
194 while offset + 16 <= len {
195 let chunk = _mm_loadu_si128(ptr.add(offset) as *const __m128i);
196
197 let ws = _mm_or_si128(
199 _mm_or_si128(_mm_cmpeq_epi8(chunk, space), _mm_cmpeq_epi8(chunk, tab)),
200 _mm_or_si128(_mm_cmpeq_epi8(chunk, nl), _mm_cmpeq_epi8(chunk, cr)),
201 );
202 let mask = _mm_movemask_epi8(ws) as u16;
203 if mask != 0xFFFF {
205 return offset + (!mask).trailing_zeros() as usize;
206 }
207 offset += 16;
208 }
209 }
210
211 offset + crate::scalar::skip_whitespace_safe(&input[offset..])
213}
214
215#[target_feature(enable = "sse4.2")]
224#[cfg(target_arch = "x86_64")]
225pub unsafe fn compute_byte_mask(block: &[u8], byte: u8) -> u64 {
226 let len = block.len().min(64);
228 let ptr = block.as_ptr();
229 let mut result: u64 = 0;
230 let mut offset = 0;
231
232 unsafe {
234 let target = _mm_set1_epi8(byte as i8);
235
236 while offset + 16 <= len {
237 let chunk = _mm_loadu_si128(ptr.add(offset) as *const __m128i);
238 let cmp = _mm_cmpeq_epi8(chunk, target);
239 let mask = _mm_movemask_epi8(cmp) as u16;
240 result |= (mask as u64) << offset;
241 offset += 16;
242 }
243 }
244
245 while offset < len {
247 if unsafe { *ptr.add(offset) } == byte {
249 result |= 1u64 << offset;
250 }
251 offset += 1;
252 }
253
254 result
255}
256
257#[cfg(all(test, target_arch = "x86_64"))]
258mod tests {
259 use super::*;
260 use crate::class;
261
262 fn has_sse42() -> bool {
263 is_x86_feature_detected!("sse4.2")
264 }
265
266 #[test]
267 fn find_delimiters_basic() {
268 if !has_sse42() {
269 return;
270 }
271 let input = b"hello world <div>";
272 let result = unsafe { find_delimiters(input) };
273 assert_eq!(
274 result,
275 DelimiterResult::Found {
276 pos: 12,
277 byte: b'<'
278 }
279 );
280 }
281
282 #[test]
283 fn find_delimiters_not_found() {
284 if !has_sse42() {
285 return;
286 }
287 let input = b"hello world no delimiters here";
288 let result = unsafe { find_delimiters(input) };
289 assert_eq!(result, DelimiterResult::NotFound);
290 }
291
292 #[test]
293 fn classify_bytes_basic() {
294 if !has_sse42() {
295 return;
296 }
297 let input = b"a1 <b2\t>Zz09&\"'/=\nhello world...";
298 let result = unsafe { classify_bytes(input) };
299 assert_eq!(result[0], class::ALPHA);
300 assert_eq!(result[1], class::DIGIT);
301 assert_eq!(result[2], class::WHITESPACE);
302 assert_eq!(result[3], class::DELIMITER);
303 }
304
305 #[test]
306 fn skip_whitespace_basic() {
307 if !has_sse42() {
308 return;
309 }
310 let result = unsafe { skip_whitespace(b" \t\nhello") };
311 assert_eq!(result, 5);
312 }
313
314 #[test]
315 fn skip_whitespace_all_ws() {
316 if !has_sse42() {
317 return;
318 }
319 let result = unsafe { skip_whitespace(b" ") };
320 assert_eq!(result, 20);
321 }
322
323 #[test]
324 fn find_delimiters_after_nul_in_simd_block() {
325 if !has_sse42() {
329 return;
330 }
331 let input = b"abc\0def<ghijklmn"; let result = unsafe { find_delimiters(input) };
333 assert_eq!(result, DelimiterResult::Found { pos: 7, byte: b'<' });
334 }
335
336 #[test]
337 fn skip_whitespace_stops_at_nul() {
338 if !has_sse42() {
340 return;
341 }
342 let input = b" \0Xhelloworld!"; let result = unsafe { skip_whitespace(input) };
344 assert_eq!(result, 3);
345 }
346}