1#[cfg(target_arch = "aarch64")]
31use std::arch::aarch64::vandq_u8;
32#[cfg(target_arch = "aarch64")]
33use std::arch::aarch64::vceqq_u8;
34#[cfg(target_arch = "aarch64")]
35use std::arch::aarch64::vcgeq_u8;
36#[cfg(target_arch = "aarch64")]
37use std::arch::aarch64::vcleq_u8;
38#[cfg(target_arch = "aarch64")]
39use std::arch::aarch64::vdupq_n_u8;
40#[cfg(target_arch = "aarch64")]
41use std::arch::aarch64::vld1q_u8;
42#[cfg(target_arch = "aarch64")]
43use std::arch::aarch64::vminvq_u8;
44#[cfg(target_arch = "aarch64")]
45use std::arch::aarch64::vorrq_u8;
46#[cfg(target_arch = "x86_64")]
47use std::arch::x86_64::__m256i;
48#[cfg(target_arch = "x86_64")]
49use std::arch::x86_64::_mm256_add_epi8;
50#[cfg(target_arch = "x86_64")]
51use std::arch::x86_64::_mm256_and_si256;
52#[cfg(target_arch = "x86_64")]
53use std::arch::x86_64::_mm256_cmpeq_epi8;
54#[cfg(target_arch = "x86_64")]
55use std::arch::x86_64::_mm256_cmpgt_epi8;
56#[cfg(target_arch = "x86_64")]
57use std::arch::x86_64::_mm256_loadu_si256;
58#[cfg(target_arch = "x86_64")]
59use std::arch::x86_64::_mm256_movemask_epi8;
60#[cfg(target_arch = "x86_64")]
61use std::arch::x86_64::_mm256_or_si256;
62#[cfg(target_arch = "x86_64")]
63use std::arch::x86_64::_mm256_set1_epi8;
64#[cfg(target_arch = "x86_64")]
65use std::arch::x86_64::_mm256_sub_epi8;
66use std::collections::HashMap;
67use std::collections::HashSet;
68use std::hash::BuildHasherDefault;
69
70use ustr::IdentityHasher;
71
72pub type Atom = ustr::Ustr;
74
75#[inline]
77#[must_use]
78pub fn atom(s: &str) -> Atom {
79 ustr::ustr(s)
80}
81
82pub type AtomMap<V> = HashMap<Atom, V, BuildHasherDefault<IdentityHasher>>;
87
88pub type AtomSet = HashSet<Atom, BuildHasherDefault<IdentityHasher>>;
93
94const STACK_BUF_SIZE: usize = 256;
96
97thread_local! {
98 static EMPTY_ATOM: Atom = atom("");
99}
100
101#[inline]
105#[must_use]
106pub fn empty_atom() -> Atom {
107 EMPTY_ATOM.with(|&atom| atom)
108}
109
110#[macro_export]
120macro_rules! concat_atom {
121 ($s1:expr, $s2:expr $(,)?) => {
122 $crate::concat_atom2(&$s1, &$s2)
123 };
124 ($s1:expr, $s2:expr, $s3:expr $(,)?) => {
125 $crate::concat_atom3(&$s1, &$s2, &$s3)
126 };
127 ($s1:expr, $s2:expr, $s3:expr, $s4:expr $(,)?) => {
128 $crate::concat_atom4(&$s1, &$s2, &$s3, &$s4)
129 };
130 ($s1:expr, $s2:expr, $s3:expr, $s4:expr, $s5:expr $(,)?) => {
131 $crate::concat_atom5(&$s1, &$s2, &$s3, &$s4, &$s5)
132 };
133 ($s1:expr, $s2:expr, $s3:expr, $s4:expr, $s5:expr, $s6:expr $(,)?) => {
134 $crate::concat_atom6(&$s1, &$s2, &$s3, &$s4, &$s5, &$s6)
135 };
136 ($s1:expr, $s2:expr, $s3:expr, $s4:expr, $s5:expr, $s6:expr, $s7:expr $(,)?) => {
137 $crate::concat_atom7(&$s1, &$s2, &$s3, &$s4, &$s5, &$s6, &$s7)
138 };
139 ($s1:expr, $s2:expr, $s3:expr, $s4:expr, $s5:expr, $s6:expr, $s7:expr, $s8:expr $(,)?) => {
140 $crate::concat_atom8(&$s1, &$s2, &$s3, &$s4, &$s5, &$s6, &$s7, &$s8)
141 };
142 ($s1:expr, $s2:expr, $s3:expr, $s4:expr, $s5:expr, $s6:expr, $s7:expr, $s8:expr, $s9:expr $(,)?) => {
143 $crate::concat_atom9(&$s1, &$s2, &$s3, &$s4, &$s5, &$s6, &$s7, &$s8, &$s9)
144 };
145 ($s1:expr, $s2:expr, $s3:expr, $s4:expr, $s5:expr, $s6:expr, $s7:expr, $s8:expr, $s9:expr, $s10:expr $(,)?) => {
146 $crate::concat_atom10(&$s1, &$s2, &$s3, &$s4, &$s5, &$s6, &$s7, &$s8, &$s9, &$s10)
147 };
148 ($s1:expr, $s2:expr, $s3:expr, $s4:expr, $s5:expr, $s6:expr, $s7:expr, $s8:expr, $s9:expr, $s10:expr, $s11:expr $(,)?) => {
149 $crate::concat_atom11(&$s1, &$s2, &$s3, &$s4, &$s5, &$s6, &$s7, &$s8, &$s9, &$s10, &$s11)
150 };
151 ($s1:expr, $s2:expr, $s3:expr, $s4:expr, $s5:expr, $s6:expr, $s7:expr, $s8:expr, $s9:expr, $s10:expr, $s11:expr, $s12:expr $(,)?) => {
152 $crate::concat_atom12(&$s1, &$s2, &$s3, &$s4, &$s5, &$s6, &$s7, &$s8, &$s9, &$s10, &$s11, &$s12)
153 };
154 ($($arg:expr),+ $(,)?) => {
155 compile_error!("concat_atom! macro supports between 2 and 12 arguments only")
156 };
157}
158
159#[inline]
165#[must_use]
166pub fn ascii_lowercase_constant_name_atom(name: &str) -> Atom {
167 if let Some(last_slash_idx) = name.rfind('\\') {
168 let (namespace, const_name) = name.split_at(last_slash_idx);
169 let const_name = &const_name[1..];
170
171 if name.len() > STACK_BUF_SIZE {
172 let mut lowercased_namespace = namespace.to_ascii_lowercase();
173 lowercased_namespace.push('\\');
174 lowercased_namespace.push_str(const_name);
175 return atom(&lowercased_namespace);
176 }
177
178 let mut stack_buf = [0u8; STACK_BUF_SIZE];
179 let mut index = 0;
180
181 for byte in namespace.bytes() {
182 stack_buf[index] = byte.to_ascii_lowercase();
183 index += 1;
184 }
185
186 stack_buf[index] = b'\\';
187 index += 1;
188
189 let const_bytes = const_name.as_bytes();
190 stack_buf[index..index + const_bytes.len()].copy_from_slice(const_bytes);
191 index += const_bytes.len();
192
193 atom(
194 unsafe { std::str::from_utf8_unchecked(&stack_buf[..index]) },
196 )
197 } else {
198 atom(name)
199 }
200}
201
202#[inline]
209#[must_use]
210pub fn ascii_lowercase_atom(s: &str) -> Atom {
211 let bytes = s.as_bytes();
212
213 if !bytes.iter().any(u8::is_ascii_uppercase) {
215 return atom(s);
216 }
217
218 if s.len() <= STACK_BUF_SIZE {
220 let mut stack_buf = [0u8; STACK_BUF_SIZE];
221 for (i, &b) in bytes.iter().enumerate() {
222 stack_buf[i] = b.to_ascii_lowercase();
223 }
224 return atom(
225 unsafe { std::str::from_utf8_unchecked(&stack_buf[..s.len()]) },
227 );
228 }
229
230 atom(&s.to_ascii_lowercase())
231}
232
233#[inline]
252#[must_use]
253pub fn starts_with_ignore_case(haystack: &str, prefix: &str) -> bool {
254 #[cfg(target_arch = "x86_64")]
255 #[target_feature(enable = "avx2")]
256 unsafe fn starts_with_avx2(haystack: &str, prefix: &str, len: usize) -> bool {
257 #[allow(clippy::multiple_unsafe_ops_per_block)]
258 unsafe {
261 let haystack_bytes = haystack.as_bytes();
262 let prefix_bytes = prefix.as_bytes();
263
264 let upper_a = _mm256_set1_epi8(b'A' as i8);
265 let upper_z = _mm256_set1_epi8(b'Z' as i8);
266 let case_bit = _mm256_set1_epi8(0x20);
267
268 let mut i = 0;
269 while i + 32 <= len {
270 #[allow(clippy::cast_ptr_alignment)]
273 let h = _mm256_loadu_si256(haystack_bytes.as_ptr().add(i).cast::<__m256i>());
274 #[allow(clippy::cast_ptr_alignment)]
275 let p = _mm256_loadu_si256(prefix_bytes.as_ptr().add(i).cast::<__m256i>());
276
277 let h_is_upper = _mm256_and_si256(
279 _mm256_cmpgt_epi8(h, _mm256_sub_epi8(upper_a, _mm256_set1_epi8(1))),
280 _mm256_cmpgt_epi8(_mm256_add_epi8(upper_z, _mm256_set1_epi8(1)), h),
281 );
282 let h_lower = _mm256_or_si256(h, _mm256_and_si256(h_is_upper, case_bit));
283
284 let p_is_upper = _mm256_and_si256(
286 _mm256_cmpgt_epi8(p, _mm256_sub_epi8(upper_a, _mm256_set1_epi8(1))),
287 _mm256_cmpgt_epi8(_mm256_add_epi8(upper_z, _mm256_set1_epi8(1)), p),
288 );
289 let p_lower = _mm256_or_si256(p, _mm256_and_si256(p_is_upper, case_bit));
290
291 let eq = _mm256_cmpeq_epi8(h_lower, p_lower);
292 let mask = _mm256_movemask_epi8(eq);
293 if mask != -1i32 {
294 return false;
295 }
296
297 i += 32;
298 }
299
300 haystack_bytes[i..len].eq_ignore_ascii_case(&prefix_bytes[i..len])
302 }
303 }
304
305 #[cfg(target_arch = "aarch64")]
306 #[target_feature(enable = "neon")]
307 unsafe fn starts_with_neon(haystack: &str, prefix: &str, len: usize) -> bool {
308 #[allow(clippy::multiple_unsafe_ops_per_block)]
309 unsafe {
313 let haystack_bytes = haystack.as_bytes();
314 let prefix_bytes = prefix.as_bytes();
315
316 let upper_a = vdupq_n_u8(b'A');
317 let upper_z = vdupq_n_u8(b'Z');
318 let case_bit = vdupq_n_u8(0x20);
319
320 let mut i = 0;
321 while i + 16 <= len {
322 let h = vld1q_u8(haystack_bytes.as_ptr().add(i));
323 let p = vld1q_u8(prefix_bytes.as_ptr().add(i));
324
325 let h_ge_a = vcgeq_u8(h, upper_a);
327 let h_le_z = vcleq_u8(h, upper_z);
328 let h_is_upper = vandq_u8(h_ge_a, h_le_z);
329 let h_lower = vorrq_u8(h, vandq_u8(h_is_upper, case_bit));
330
331 let p_ge_a = vcgeq_u8(p, upper_a);
333 let p_le_z = vcleq_u8(p, upper_z);
334 let p_is_upper = vandq_u8(p_ge_a, p_le_z);
335 let p_lower = vorrq_u8(p, vandq_u8(p_is_upper, case_bit));
336
337 let eq = vceqq_u8(h_lower, p_lower);
338 let min = vminvq_u8(eq);
339 if min != 0xFF {
340 return false;
341 }
342
343 i += 16;
344 }
345
346 haystack_bytes[i..len].eq_ignore_ascii_case(&prefix_bytes[i..len])
348 }
349 }
350
351 let len = prefix.len();
352 if haystack.len() < len {
353 return false;
354 }
355
356 #[cfg(target_arch = "x86_64")]
357 {
358 if len >= 32 && std::is_x86_feature_detected!("avx2") {
359 return unsafe { starts_with_avx2(haystack, prefix, len) };
361 }
362 }
363
364 #[cfg(target_arch = "aarch64")]
365 {
366 if len >= 16 {
367 return unsafe { starts_with_neon(haystack, prefix, len) };
369 }
370 }
371
372 haystack.as_bytes()[..len].eq_ignore_ascii_case(prefix.as_bytes())
373}
374
375macro_rules! integer_to_atom_fns {
377 ( $( $func_name:ident($num_type:ty) ),+ $(,)? ) => {
378 $(
379 #[doc = "Creates an `Atom` from a `"]
380 #[doc = stringify!($num_type)]
381 #[doc = "` value with zero heap allocations."]
382 #[inline]
383 #[must_use]
384 pub fn $func_name(n: $num_type) -> Atom {
385 let mut buffer = itoa::Buffer::new();
386 let s = buffer.format(n);
387
388 atom(s)
389 }
390 )+
391 };
392}
393
394macro_rules! float_to_atom_fns {
396 ( $( $func_name:ident($num_type:ty) ),+ $(,)? ) => {
397 $(
398 #[doc = "Creates an `Atom` from a `"]
399 #[doc = stringify!($num_type)]
400 #[doc = "` value with zero heap allocations."]
401 #[inline]
402 #[must_use]
403 pub fn $func_name(n: $num_type) -> Atom {
404 let mut buffer = ryu::Buffer::new();
405 let s = buffer.format(n);
406
407 atom(s)
408 }
409 )+
410 };
411}
412
413macro_rules! concat_fns {
415 ( $( $func_name:ident($n:literal, $($s:ident),+) ),+ $(,)?) => {
416 $(
417 #[doc = "Creates an `Atom` as a result of concatenating "]
418 #[doc = stringify!($n)]
419 #[doc = " string slices."]
420 #[inline]
421 #[must_use]
422 #[allow(unused_assignments)]
423 #[allow(clippy::too_many_arguments)]
424 pub fn $func_name($($s: &str),+) -> Atom {
425 let total_len = 0 $(+ $s.len())+;
426
427 if total_len <= STACK_BUF_SIZE {
428 let mut buffer = [0u8; STACK_BUF_SIZE];
429 let mut index = 0;
430 $(
431 buffer[index..index + $s.len()].copy_from_slice($s.as_bytes());
432 index += $s.len();
433 )+
434
435 return atom(
436 unsafe { std::str::from_utf8_unchecked(&buffer[..total_len]) },
439 );
440 }
441
442 let mut result = String::with_capacity(total_len);
444 $( result.push_str($s); )+
445 atom(&result)
446 }
447 )+
448 };
449}
450
451integer_to_atom_fns!(
453 i8_atom(i8),
454 i16_atom(i16),
455 i32_atom(i32),
456 i64_atom(i64),
457 i128_atom(i128),
458 isize_atom(isize),
459 u8_atom(u8),
460 u16_atom(u16),
461 u32_atom(u32),
462 u64_atom(u64),
463 u128_atom(u128),
464 usize_atom(usize),
465);
466
467float_to_atom_fns!(f32_atom(f32), f64_atom(f64),);
468
469concat_fns!(
470 concat_atom2(2, s1, s2),
471 concat_atom3(3, s1, s2, s3),
472 concat_atom4(4, s1, s2, s3, s4),
473 concat_atom5(5, s1, s2, s3, s4, s5),
474 concat_atom6(6, s1, s2, s3, s4, s5, s6),
475 concat_atom7(7, s1, s2, s3, s4, s5, s6, s7),
476 concat_atom8(8, s1, s2, s3, s4, s5, s6, s7, s8),
477 concat_atom9(9, s1, s2, s3, s4, s5, s6, s7, s8, s9),
478 concat_atom10(10, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10),
479 concat_atom11(11, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11),
480 concat_atom12(12, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12),
481);