#[cfg(feature = "simd-transcode")]
use fearless_simd::u16x8;
use fearless_simd::{Level, Simd, dispatch, prelude::*, u8x16, u32x4};
#[cfg(feature = "simd-transcode")]
use crate::utf8::decode_utf8_n;
#[cfg(feature = "simd-transcode")]
use crate::utf8_tables::{SHUFUTF8, UTF8BIGINDEX};
#[inline(always)]
pub(crate) fn first_non_printable(bytes: &[u8]) -> usize {
if let Some(&b) = bytes.first()
&& !(0x20..=0x7e).contains(&b)
{
return 0;
}
let level = Level::new();
dispatch!(level, simd => first_matching(simd, bytes, ScanKind::NonPrintable))
}
#[inline(always)]
pub(crate) fn first_esc(bytes: &[u8]) -> usize {
let level = Level::new();
dispatch!(level, simd => first_matching(simd, bytes, ScanKind::Esc))
}
#[derive(Clone, Copy)]
enum ScanKind {
NonPrintable,
Esc,
}
#[inline(always)]
fn first_matching<S: Simd>(simd: S, bytes: &[u8], kind: ScanKind) -> usize {
let (chunks, remainder) = bytes.as_chunks::<16>();
let mut offset = 0;
for chunk in chunks {
let v = u8x16::from_slice(simd, chunk);
let special = match kind {
ScanKind::NonPrintable => {
simd.simd_lt_u8x16(v, simd.splat_u8x16(0x20))
| simd.simd_ge_u8x16(v, simd.splat_u8x16(0x7f))
}
ScanKind::Esc => {
simd.simd_eq_u8x16(v, simd.splat_u8x16(0x1b))
}
};
if simd.any_true_mask8x16(special) {
let mask: [i8; 16] = special.into();
return offset + first_set(mask);
}
offset += 16;
}
if let Some(j) = remainder.iter().position(|&b| matches_kind(b, kind)) {
return offset + j;
}
bytes.len()
}
#[inline]
fn matches_kind(b: u8, kind: ScanKind) -> bool {
match kind {
ScanKind::NonPrintable => !(0x20..=0x7e).contains(&b),
ScanKind::Esc => b == 0x1b,
}
}
#[inline(always)]
fn first_set(mask: [i8; 16]) -> usize {
mask.iter().position(|&m| m != 0).unwrap_or(16)
}
#[inline(always)]
fn decode_3byte<S: Simd>(simd: S, bytes: &[u8]) -> [u32; 4] {
let v = u8x16::from_slice(simd, bytes);
let sh = u8x16::from_slice(
simd,
&[2, 1, 0, 0xff, 5, 4, 3, 0xff, 8, 7, 6, 0xff, 11, 10, 9, 0xff],
);
let perm = simd.swizzle_dyn_precise_u8x16(v, sh);
let p: u32x4<S> = perm.bitcast();
let ascii = p & simd.splat_u32x4(0x7f);
let middle = simd.shr_u32x4(p & simd.splat_u32x4(0x3f00), 2);
let high = simd.shr_u32x4(p & simd.splat_u32x4(0x0f0000), 4);
((ascii | middle) | high).into()
}
#[cfg(not(feature = "simd-transcode"))]
#[inline(always)]
pub(crate) fn decode_3byte_dispatch(bytes: &[u8]) -> [u32; 4] {
let level = Level::new();
dispatch!(level, simd => decode_3byte(simd, bytes))
}
#[cfg(feature = "simd-transcode")]
#[inline(always)]
fn decode_utf8_12<S: Simd>(
simd: S,
bytes: &[u8],
end_mask: u64,
out: &mut [u32],
) -> (usize, usize) {
let v = u8x16::from_slice(simd, bytes);
let m12 = (end_mask & 0xfff) as usize;
if m12 == 0x924 {
let arr = decode_3byte(simd, bytes);
out[..4].copy_from_slice(&arr);
return (4, 12);
}
let idx = UTF8BIGINDEX[m12][0] as usize;
let consumed = UTF8BIGINDEX[m12][1] as usize;
if idx < 64 {
let sh = u8x16::from_slice(simd, &SHUFUTF8[idx]);
let perm = simd.swizzle_dyn_precise_u8x16(v, sh);
let p: u16x8<S> = perm.bitcast();
let ascii = p & simd.splat_u16x8(0x7f);
let high = simd.shr_u16x8(p & simd.splat_u16x8(0x1f00), 2);
let composed = ascii | high;
let (lo, hi) = composed.widen();
let lo: [u32; 4] = lo.into();
let hi: [u32; 4] = hi.into();
out[..4].copy_from_slice(&lo);
out[4..6].copy_from_slice(&hi[..2]);
(6, consumed)
} else if idx < 145 {
let sh = u8x16::from_slice(simd, &SHUFUTF8[idx]);
let perm = simd.swizzle_dyn_precise_u8x16(v, sh);
let p: u32x4<S> = perm.bitcast();
let ascii = p & simd.splat_u32x4(0x7f);
let middle = simd.shr_u32x4(p & simd.splat_u32x4(0x3f00), 2);
let high = simd.shr_u32x4(p & simd.splat_u32x4(0x0f0000), 4);
let composed = (ascii | middle) | high;
let arr: [u32; 4] = composed.into();
out[..4].copy_from_slice(&arr);
(4, consumed)
} else if idx < 209 {
let sh = u8x16::from_slice(simd, &SHUFUTF8[idx]);
let perm = simd.swizzle_dyn_precise_u8x16(v, sh);
let p: u32x4<S> = perm.bitcast();
let ascii = p & simd.splat_u32x4(0x7f);
let middle = simd.shr_u32x4(p & simd.splat_u32x4(0x3f00), 2);
let correct = simd.shr_u32x4(p & simd.splat_u32x4(0x400000), 1);
let middlehigh = simd.shr_u32x4(correct ^ (p & simd.splat_u32x4(0x3f0000)), 4);
let high = simd.shr_u32x4(p & simd.splat_u32x4(0x07000000), 6);
let composed = (ascii | middle) | (high | middlehigh);
let arr: [u32; 4] = composed.into();
out[..3].copy_from_slice(&arr[..3]);
(3, consumed)
} else {
(0, consumed)
}
}
#[cfg(feature = "simd-transcode")]
#[inline(always)]
fn decode_block<S: Simd>(simd: S, bytes: &[u8], out: &mut [u32]) -> (usize, usize) {
let n = bytes.len();
let mut written = 0;
let mut pos = 0;
if n >= 76 {
let mut cont = 0_u64;
for k in 0..4 {
let v = u8x16::from_slice(simd, &bytes[k * 16..k * 16 + 16]);
let m = simd.simd_eq_u8x16(v & simd.splat_u8x16(0xc0), simd.splat_u8x16(0x80));
cont |= simd.to_bitmask_mask8x16(m) << (k * 16);
}
let mut end_mask = !(cont >> 1);
while pos < 52 {
let (count, consumed) =
decode_utf8_12(simd, &bytes[pos..pos + 16], end_mask, &mut out[written..]);
written += count;
pos += consumed;
end_mask >>= consumed;
}
return (written, pos);
}
while pos < n {
let b0 = bytes[pos];
let (cp, len) = match b0 {
0x00..=0x7f => (b0 as u32, 1),
0xe0..=0xef => (decode_utf8_n::<3>(&bytes[pos..]), 3),
0xf0..=0xf4 => (decode_utf8_n::<4>(&bytes[pos..]), 4),
_ => (decode_utf8_n::<2>(&bytes[pos..]), 2),
};
out[written] = cp;
written += 1;
pos += len;
}
(written, pos)
}
#[cfg(feature = "simd-transcode")]
#[inline(always)]
pub(crate) fn decode_block_dispatch(bytes: &[u8], out: &mut [u32]) -> (usize, usize) {
let level = Level::new();
dispatch!(level, simd => decode_block(simd, bytes, out))
}
#[cfg(feature = "simd-transcode")]
#[inline(always)]
pub(crate) fn any_control(cps: &[u32]) -> bool {
let level = Level::new();
dispatch!(level, simd => any_control_impl(simd, cps))
}
#[cfg(feature = "simd-transcode")]
fn any_control_impl<S: Simd>(simd: S, cps: &[u32]) -> bool {
let (chunks, tail) = cps.as_chunks::<4>();
let mut any = false;
for chunk in chunks {
let v = u32x4::from_slice(simd, chunk);
let c0 = simd.simd_lt_u32x4(v, simd.splat_u32x4(0x20));
let c1 = simd.simd_ge_u32x4(v, simd.splat_u32x4(0x7f))
& simd.simd_le_u32x4(v, simd.splat_u32x4(0x9f));
any |= simd.any_true_mask32x4(c0 | c1);
}
if any {
return true;
}
tail.iter()
.any(|&cp| cp < 0x20 || (0x7f..=0x9f).contains(&cp))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_3byte_decodes_four_cjk() {
let bytes = [
0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87, 0xe6, 0x97, 0xa5, 0xe6, 0x9c, 0xac, 0, 0, 0, 0,
];
let out = dispatch!(Level::new(), simd => decode_3byte(simd, &bytes));
assert_eq!(out, [0x4e2d, 0x6587, 0x65e5, 0x672c]);
}
#[test]
#[cfg(feature = "simd-transcode")]
fn decode_block_decodes_mixed_utf8() {
let s = "a日🚀e日";
let mut bytes = s.as_bytes().to_vec();
bytes.resize(128, b' ');
let mut out = [0_u32; 76];
let mut cps = Vec::new();
let mut i = 0;
while i < bytes.len() {
let (c, k) = decode_block_dispatch(&bytes[i..], &mut out);
cps.extend_from_slice(&out[..c]);
i += k;
}
let decoded: Vec<u32> = cps
.into_iter()
.take_while(|&cp| cp != b' ' as u32)
.collect();
let expected: [u32; 5] = ['a', '日', '🚀', 'e', '日'].map(|c| c as u32);
assert_eq!(decoded.as_slice(), expected.as_slice());
}
#[test]
fn first_set_finds_first_lane() {
for j in 0..16 {
let mut m = [0_i8; 16];
m[j] = -1;
assert_eq!(first_set(m), j, "lane {j}");
}
let mut m = [0_i8; 16];
m[3] = -1;
m[11] = -1;
assert_eq!(first_set(m), 3);
}
}