#![cfg_attr(feature = "simd", allow(unsafe_code))]
#[inline]
pub(crate) fn find_escape(bytes: &[u8], escape_non_ascii: bool) -> Option<usize> {
imp::scan(bytes, escape_non_ascii)
}
#[inline]
pub(crate) fn find_string_special(bytes: &[u8]) -> Option<usize> {
imp::scan(bytes, false)
}
#[inline]
pub(crate) fn skip_whitespace(input: &[u8], pos: usize) -> usize {
imp::skip_ws(input, pos)
}
#[inline]
fn escape_byte(byte: u8, check_non_ascii: bool) -> bool {
byte < 0x20 || byte == b'"' || byte == b'\\' || (check_non_ascii && byte >= 0x80)
}
#[cfg(test)]
#[inline]
fn string_special_byte(byte: u8) -> bool {
byte < 0x20 || byte == b'"' || byte == b'\\'
}
#[inline]
fn scan_scalar(bytes: &[u8], check_non_ascii: bool) -> Option<usize> {
bytes.iter().position(|&b| escape_byte(b, check_non_ascii))
}
fn skip_ws_scalar(input: &[u8], mut pos: usize) -> usize {
while pos < input.len() && matches!(input[pos], b' ' | b'\t' | b'\n' | b'\r') {
pos += 1;
}
pos
}
#[cfg(not(any(
all(feature = "simd", target_arch = "x86_64"),
all(feature = "simd", target_arch = "aarch64")
)))]
mod portable {
#[cfg(not(target_pointer_width = "64"))]
#[inline]
fn chunk_has_escape(chunk: u64, check_non_ascii: bool) -> bool {
if (chunk.wrapping_sub(0x2020_2020_2020_2020)) & !chunk & 0x8080_8080_8080_8080 != 0 {
return true;
}
let quote = chunk ^ 0x2222_2222_2222_2222;
if (quote.wrapping_sub(0x0101_0101_0101_0101)) & !quote & 0x8080_8080_8080_8080 != 0 {
return true;
}
let backslash = chunk ^ 0x5C5C_5C5C_5C5C_5C5C;
if (backslash.wrapping_sub(0x0101_0101_0101_0101)) & !backslash & 0x8080_8080_8080_8080 != 0
{
return true;
}
check_non_ascii && (chunk & 0x8080_8080_8080_8080) != 0
}
#[cfg(target_pointer_width = "64")]
#[inline]
fn chunk_has_escape(chunk: u128, check_non_ascii: bool) -> bool {
if (chunk.wrapping_sub(0x2020_2020_2020_2020_2020_2020_2020_2020))
& !chunk
& 0x8080_8080_8080_8080_8080_8080_8080_8080
!= 0
{
return true;
}
let quote = chunk ^ 0x2222_2222_2222_2222_2222_2222_2222_2222;
if (quote.wrapping_sub(0x0101_0101_0101_0101_0101_0101_0101_0101))
& !quote
& 0x8080_8080_8080_8080_8080_8080_8080_8080
!= 0
{
return true;
}
let backslash = chunk ^ 0x5C5C_5C5C_5C5C_5C5C_5C5C_5C5C_5C5C_5C5C;
if (backslash.wrapping_sub(0x0101_0101_0101_0101_0101_0101_0101_0101))
& !backslash
& 0x8080_8080_8080_8080_8080_8080_8080_8080
!= 0
{
return true;
}
check_non_ascii && (chunk & 0x8080_8080_8080_8080_8080_8080_8080_8080) != 0
}
#[cfg(target_pointer_width = "64")]
#[inline]
pub(super) fn scan(bytes: &[u8], check_non_ascii: bool) -> Option<usize> {
let len = bytes.len();
let mut i = 0;
while i + 16 <= len {
let chunk = u128::from_le_bytes(bytes[i..i + 16].try_into().unwrap());
if chunk_has_escape(chunk, check_non_ascii) {
return super::scan_scalar(&bytes[i..], check_non_ascii).map(|off| i + off);
}
i += 16;
}
super::scan_scalar(&bytes[i..], check_non_ascii).map(|off| i + off)
}
#[cfg(not(target_pointer_width = "64"))]
#[inline]
pub(super) fn scan(bytes: &[u8], check_non_ascii: bool) -> Option<usize> {
let len = bytes.len();
let mut i = 0;
while i + 8 <= len {
let chunk = u64::from_le_bytes(bytes[i..i + 8].try_into().unwrap());
if chunk_has_escape(chunk, check_non_ascii) {
return super::scan_scalar(&bytes[i..], check_non_ascii).map(|off| i + off);
}
i += 8;
}
super::scan_scalar(&bytes[i..], check_non_ascii).map(|off| i + off)
}
#[cfg(target_pointer_width = "64")]
#[inline]
pub(super) fn skip_ws(input: &[u8], mut pos: usize) -> usize {
let len = input.len();
while pos + 16 <= len {
let chunk = u128::from_le_bytes(input[pos..pos + 16].try_into().unwrap());
if !chunk_all_ws(chunk) {
break;
}
pos += 16;
}
super::skip_ws_scalar(input, pos)
}
#[cfg(not(target_pointer_width = "64"))]
#[inline]
pub(super) fn skip_ws(input: &[u8], pos: usize) -> usize {
super::skip_ws_scalar(input, pos)
}
#[cfg(target_pointer_width = "64")]
#[inline]
fn chunk_all_ws(chunk: u128) -> bool {
fn haszero(x: u128, b: u128) -> u128 {
let x = x ^ b;
(x.wrapping_sub(0x0101_0101_0101_0101_0101_0101_0101_0101))
& !x
& 0x8080_8080_8080_8080_8080_8080_8080_8080
}
let m = haszero(chunk, 0x2020_2020_2020_2020_2020_2020_2020_2020)
| haszero(chunk, 0x0909_0909_0909_0909_0909_0909_0909_0909)
| haszero(chunk, 0x0A0A_0A0A_0A0A_0A0A_0A0A_0A0A_0A0A_0A0A)
| haszero(chunk, 0x0D0D_0D0D_0D0D_0D0D_0D0D_0D0D_0D0D_0D0D);
m == 0x8080_8080_8080_8080_8080_8080_8080_8080
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
mod imp {
use super::{scan_scalar, skip_ws_scalar};
pub(super) fn scan(bytes: &[u8], check_non_ascii: bool) -> Option<usize> {
if bytes.len() < 32 {
return scan_scalar(bytes, check_non_ascii);
}
#[cfg(feature = "std")]
if std::is_x86_feature_detected!("avx2") {
return unsafe { scan_avx2(bytes, check_non_ascii) };
}
unsafe { scan_sse2(bytes, check_non_ascii) }
}
pub(super) fn skip_ws(input: &[u8], pos: usize) -> usize {
if input.len().saturating_sub(pos) < 32 {
return skip_ws_scalar(input, pos);
}
#[cfg(feature = "std")]
if std::is_x86_feature_detected!("avx2") {
return unsafe { skip_ws_avx2(input, pos) };
}
unsafe { skip_ws_sse2(input, pos) }
}
#[inline]
unsafe fn scan_sse2(bytes: &[u8], check_non_ascii: bool) -> Option<usize> {
use core::arch::x86_64::*;
let ptr = bytes.as_ptr();
let len = bytes.len();
let quote = _mm_set1_epi8(b'"' as i8);
let backslash = _mm_set1_epi8(b'\\' as i8);
let control = _mm_set1_epi8(0x1f);
let high = _mm_set1_epi8(0x80_u8 as i8);
let mut i = 0usize;
while i + 16 <= len {
let data = _mm_loadu_si128(ptr.add(i) as *const __m128i);
let ctl = _mm_cmpeq_epi8(_mm_min_epu8(data, control), data);
let q = _mm_cmpeq_epi8(data, quote);
let bs = _mm_cmpeq_epi8(data, backslash);
let mut mask = _mm_movemask_epi8(_mm_or_si128(_mm_or_si128(ctl, q), bs));
if check_non_ascii {
mask |= _mm_movemask_epi8(_mm_and_si128(data, high));
}
if mask != 0 {
return Some(i + mask.trailing_zeros() as usize);
}
i += 16;
}
scan_scalar(&bytes[i..], check_non_ascii).map(|off| i + off)
}
#[cfg(feature = "std")]
#[target_feature(enable = "avx2")]
unsafe fn scan_avx2(bytes: &[u8], check_non_ascii: bool) -> Option<usize> {
use core::arch::x86_64::*;
let ptr = bytes.as_ptr();
let len = bytes.len();
let quote = _mm256_set1_epi8(b'"' as i8);
let backslash = _mm256_set1_epi8(b'\\' as i8);
let control = _mm256_set1_epi8(0x1f);
let high = _mm256_set1_epi8(0x80_u8 as i8);
let mut i = 0usize;
while i + 32 <= len {
let data = _mm256_loadu_si256(ptr.add(i) as *const __m256i);
let ctl = _mm256_cmpeq_epi8(_mm256_min_epu8(data, control), data);
let q = _mm256_cmpeq_epi8(data, quote);
let bs = _mm256_cmpeq_epi8(data, backslash);
let mut mask = _mm256_movemask_epi8(_mm256_or_si256(_mm256_or_si256(ctl, q), bs));
if check_non_ascii {
mask |= _mm256_movemask_epi8(_mm256_and_si256(data, high));
}
if mask != 0 {
return Some(i + mask.trailing_zeros() as usize);
}
i += 32;
}
scan_sse2(&bytes[i..], check_non_ascii).map(|off| i + off)
}
#[inline]
unsafe fn skip_ws_sse2(input: &[u8], mut pos: usize) -> usize {
use core::arch::x86_64::*;
let ptr = input.as_ptr();
let len = input.len();
let space = _mm_set1_epi8(b' ' as i8);
let tab = _mm_set1_epi8(b'\t' as i8);
let lf = _mm_set1_epi8(b'\n' as i8);
let cr = _mm_set1_epi8(b'\r' as i8);
while pos + 16 <= len {
let data = _mm_loadu_si128(ptr.add(pos) as *const __m128i);
let any = _mm_or_si128(
_mm_or_si128(_mm_cmpeq_epi8(data, space), _mm_cmpeq_epi8(data, tab)),
_mm_or_si128(_mm_cmpeq_epi8(data, lf), _mm_cmpeq_epi8(data, cr)),
);
let mask = _mm_movemask_epi8(any);
if mask != 0xFFFF {
return skip_ws_scalar(input, pos + mask.trailing_ones() as usize);
}
pos += 16;
}
skip_ws_scalar(input, pos)
}
#[cfg(feature = "std")]
#[target_feature(enable = "avx2")]
unsafe fn skip_ws_avx2(input: &[u8], mut pos: usize) -> usize {
use core::arch::x86_64::*;
let ptr = input.as_ptr();
let len = input.len();
let space = _mm256_set1_epi8(b' ' as i8);
let tab = _mm256_set1_epi8(b'\t' as i8);
let lf = _mm256_set1_epi8(b'\n' as i8);
let cr = _mm256_set1_epi8(b'\r' as i8);
while pos + 32 <= len {
let data = _mm256_loadu_si256(ptr.add(pos) as *const __m256i);
let any = _mm256_or_si256(
_mm256_or_si256(_mm256_cmpeq_epi8(data, space), _mm256_cmpeq_epi8(data, tab)),
_mm256_or_si256(_mm256_cmpeq_epi8(data, lf), _mm256_cmpeq_epi8(data, cr)),
);
let mask = _mm256_movemask_epi8(any);
if mask != -1 {
return skip_ws_scalar(input, pos + mask.trailing_ones() as usize);
}
pos += 32;
}
skip_ws_sse2(input, pos)
}
}
#[cfg(all(feature = "simd", target_arch = "aarch64"))]
mod imp {
use super::{scan_scalar, skip_ws_scalar};
pub(super) fn scan(bytes: &[u8], check_non_ascii: bool) -> Option<usize> {
if bytes.len() < 32 {
return scan_scalar(bytes, check_non_ascii);
}
unsafe { scan_neon(bytes, check_non_ascii) }
}
pub(super) fn skip_ws(input: &[u8], pos: usize) -> usize {
if input.len().saturating_sub(pos) < 32 {
return skip_ws_scalar(input, pos);
}
unsafe { skip_ws_neon(input, pos) }
}
#[inline]
unsafe fn scan_neon(bytes: &[u8], check_non_ascii: bool) -> Option<usize> {
use core::arch::aarch64::*;
let ptr = bytes.as_ptr();
let len = bytes.len();
let quote = vdupq_n_u8(b'"');
let backslash = vdupq_n_u8(b'\\');
let control = vdupq_n_u8(0x1F);
let high_gt = vdupq_n_u8(0x7F);
let mut i = 0usize;
while i + 16 <= len {
let data = vld1q_u8(ptr.add(i));
let ctl = vceqq_u8(vminq_u8(data, control), data);
let mut combined = vorrq_u8(
vorrq_u8(ctl, vceqq_u8(data, quote)),
vceqq_u8(data, backslash),
);
if check_non_ascii {
combined = vorrq_u8(combined, vcgtq_u8(data, high_gt));
}
if vmaxvq_u8(combined) == 0xFF {
return Some(
i + scan_scalar(&bytes[i..i + 16], check_non_ascii)
.expect("NEON match implies scalar match"),
);
}
i += 16;
}
scan_scalar(&bytes[i..], check_non_ascii).map(|off| i + off)
}
#[inline]
unsafe fn skip_ws_neon(input: &[u8], mut pos: usize) -> usize {
use core::arch::aarch64::*;
let ptr = input.as_ptr();
let len = input.len();
let space = vdupq_n_u8(b' ');
let tab = vdupq_n_u8(b'\t');
let lf = vdupq_n_u8(b'\n');
let cr = vdupq_n_u8(b'\r');
while pos + 16 <= len {
let data = vld1q_u8(ptr.add(pos));
let any = vorrq_u8(
vorrq_u8(vceqq_u8(data, space), vceqq_u8(data, tab)),
vorrq_u8(vceqq_u8(data, lf), vceqq_u8(data, cr)),
);
if vminvq_u8(any) != 0xFF {
return skip_ws_scalar(input, pos);
}
pos += 16;
}
skip_ws_scalar(input, pos)
}
}
#[cfg(not(any(
all(feature = "simd", target_arch = "x86_64"),
all(feature = "simd", target_arch = "aarch64")
)))]
mod imp {
pub(super) use super::portable::{scan, skip_ws};
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
#[test]
fn scan_matches_scalar_reference() {
let mut buf = Vec::new();
for a in 0..=255u8 {
buf.clear();
buf.push(a);
for (esc, got) in [
(find_escape(&buf, false), scan_scalar(&buf, false)),
(find_escape(&buf, true), scan_scalar(&buf, true)),
(find_string_special(&buf), {
buf.iter().position(|&b| string_special_byte(b))
}),
] {
assert_eq!(got, esc, "single byte 0x{a:02X}");
}
}
for a in 0..=255u8 {
for b in 0..=255u8 {
buf.clear();
buf.push(a);
buf.push(b);
assert_eq!(
find_escape(&buf, false),
scan_scalar(&buf, false),
"pair {a:02X},{b:02X}"
);
assert_eq!(
find_escape(&buf, true),
scan_scalar(&buf, true),
"pair non-ascii {a:02X},{b:02X}"
);
assert_eq!(
find_string_special(&buf),
buf.iter().position(|&x| string_special_byte(x)),
"pair special {a:02X},{b:02X}"
);
}
}
}
#[test]
fn scan_lengths_and_patterns() {
let interesting = [
0x00u8, 0x08, 0x09, 0x0A, 0x0D, 0x1F, 0x20, 0x21, 0x22, 0x5B, 0x5C, 0x5D, 0x7E, 0x7F,
0x80, 0xC3, 0xE4, 0xFF,
];
for len in 0..=80usize {
let mut clean = vec![b'a'; len];
assert_eq!(find_escape(&clean, false), scan_scalar(&clean, false));
assert_eq!(find_escape(&clean, true), scan_scalar(&clean, true));
assert_eq!(find_string_special(&clean), scan_scalar(&clean, false));
if len > 0 {
for &byte in &interesting {
for pos in [0usize, len / 2, len - 1] {
clean[pos] = byte;
assert_eq!(
find_escape(&clean, false),
scan_scalar(&clean, false),
"len {len} byte {byte:02X} at {pos}"
);
assert_eq!(
find_escape(&clean, true),
scan_scalar(&clean, true),
"len {len} byte {byte:02X} at {pos} (na)"
);
assert_eq!(
find_string_special(&clean),
scan_scalar(&clean, false),
"len {len} byte {byte:02X} at {pos} (special)"
);
clean[pos] = b'a';
}
}
}
}
let big = vec![b'x'; 1 << 20];
assert_eq!(find_escape(&big, false), None);
assert_eq!(find_escape(&big, true), None);
assert_eq!(find_string_special(&big), None);
let mut sweeps = vec![b'x'; 4096];
for pos in [0usize, 1, 15, 16, 31, 32, 33, 63, 64, 100, 4095] {
sweeps[pos] = b'"';
let expect = Some(pos);
assert_eq!(find_escape(&sweeps, false), expect, "sweep {pos}");
assert_eq!(find_string_special(&sweeps), expect, "sweep special {pos}");
sweeps[pos] = b'x';
}
let multi = b"aaa\"bbb\\ccc\x01ddd";
assert_eq!(find_escape(multi, false), Some(3));
assert_eq!(find_string_special(multi), Some(3));
let non_ascii_first = [0xC3u8, 0xA9, b'x', b'y', b'z'];
assert_eq!(find_escape(&non_ascii_first, false), None);
assert_eq!(find_escape(&non_ascii_first, true), Some(0));
}
#[test]
fn skip_whitespace_matches_scalar() {
let mut buf = Vec::new();
for len in 0..=100usize {
for kind in 0..8usize {
buf.clear();
for _ in 0..len {
buf.push(match (kind + len) % 4 {
0 => b' ',
1 => b'\t',
2 => b'\n',
_ => b'\r',
});
}
let scalar = skip_ws_scalar(&buf, 0);
assert_eq!(
skip_whitespace(&buf, 0),
scalar,
"run len {len} kind {kind}"
);
assert_eq!(skip_whitespace(&buf, 5), skip_ws_scalar(&buf, 5), "offset");
}
}
let cases: &[&[u8]] = &[
b"",
b" ",
b"\t\n\r ",
b" \t\n\r x",
b" \t\n\rx y",
b"x",
b" x ",
b" x ",
b" \n\r\t xyz",
];
for case in cases {
for start in 0..=case.len() {
assert_eq!(
skip_whitespace(case, start),
skip_ws_scalar(case, start),
"case {case:?} start {start}"
);
}
}
let mut big = vec![b'\n'; 4096];
big.extend_from_slice(b"content");
assert_eq!(skip_whitespace(&big, 0), 4096);
assert_eq!(skip_whitespace(&big, 4090), 4096);
let all = vec![b' '; 4096];
assert_eq!(skip_whitespace(&all, 0), 4096);
}
#[test]
fn no_panic_on_any_input() {
let mut state = 0x1234_5678_9ABC_DEF0u64;
let mut rng = move || {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(state >> 33) as u8
};
for _ in 0..2000 {
let len = (rng() as usize) % 80;
let buf: Vec<u8> = (0..len).map(|_| rng()).collect();
for end in 0..=len {
let slice = &buf[..end];
assert_eq!(find_escape(slice, false), scan_scalar(slice, false));
assert_eq!(find_escape(slice, true), scan_scalar(slice, true));
assert_eq!(find_string_special(slice), scan_scalar(slice, false));
assert_eq!(skip_whitespace(slice, 0), skip_ws_scalar(slice, 0));
}
}
}
}