use core::arch::x86_64::*;
use crate::alphabet::Alphabet;
use crate::wide::{expand, field_starts, gather_bytes};
#[cfg(miri)]
use shims::{_mm512_multishift_epi64_epi8, _mm512_permutexvar_epi8};
#[cfg(miri)]
mod shims {
use core::arch::x86_64::__m512i;
use core::mem::transmute;
#[inline]
pub unsafe fn _mm512_permutexvar_epi8(index: __m512i, data: __m512i) -> __m512i {
unsafe {
let index: [u8; 64] = transmute(index);
let data: [u8; 64] = transmute(data);
let mut out = [0u8; 64];
for (slot, &which) in out.iter_mut().zip(index.iter()) {
*slot = data[usize::from(which & 63)];
}
transmute(out)
}
}
#[inline]
pub unsafe fn _mm512_multishift_epi64_epi8(control: __m512i, data: __m512i) -> __m512i {
unsafe {
let control: [u8; 64] = transmute(control);
let lanes: [u64; 8] = transmute(data);
let mut out = [0u8; 64];
for (slot, (&start, lane)) in out
.iter_mut()
.zip(control.iter().zip(lanes.iter().flat_map(|lane| [lane; 8])))
{
*slot = lane.rotate_right(u32::from(start & 63)) as u8;
}
transmute(out)
}
}
}
const BLOCK_GROUPS: usize = 16;
const CHECK_EVERY: usize = 4;
const _: () = assert!(CHECK_EVERY == 4);
static EXPAND: [i8; 64] = expand();
static FIELD_STARTS: [i8; 64] = field_starts();
static GATHER: [i8; 64] = gather_bytes();
#[target_feature(enable = "avx512f,avx512bw,avx512vbmi")]
pub(crate) unsafe fn encode_bulk<A: Alphabet>(
input: &[u8],
out: &mut [u8],
groups: usize,
) -> usize {
if groups == 0 || groups * 3 > input.len() || groups * 4 > out.len() {
return 0;
}
unsafe {
let alphabet = _mm512_loadu_si512(A::ENCODE.as_ptr().cast());
let expand = _mm512_loadu_si512(EXPAND.as_ptr().cast());
let starts = _mm512_loadu_si512(FIELD_STARTS.as_ptr().cast());
let source = input.as_ptr();
let destination = out.as_mut_ptr();
let whole = mask_for(BLOCK_GROUPS * 3);
let mut done = 0;
while groups - done >= BLOCK_GROUPS {
let data = _mm512_maskz_loadu_epi8(whole, source.add(done * 3).cast());
let spread = _mm512_permutexvar_epi8(expand, data);
let fields = _mm512_multishift_epi64_epi8(starts, spread);
let spelled = _mm512_permutexvar_epi8(fields, alphabet);
_mm512_storeu_si512(destination.add(done * 4).cast(), spelled);
done += BLOCK_GROUPS;
}
if done < groups {
let taking = groups - done;
let data = _mm512_maskz_loadu_epi8(mask_for(taking * 3), source.add(done * 3).cast());
let spread = _mm512_permutexvar_epi8(expand, data);
let fields = _mm512_multishift_epi64_epi8(starts, spread);
let spelled = _mm512_permutexvar_epi8(fields, alphabet);
_mm512_mask_storeu_epi8(
destination.add(done * 4).cast(),
mask_for(taking * 4),
spelled,
);
}
groups
}
}
#[inline(always)]
fn mask_for(count: usize) -> __mmask64 {
if count >= 64 {
!0
} else {
(1u64 << count) - 1
}
}
#[target_feature(enable = "avx512f,avx512bw,avx512vbmi")]
pub(crate) unsafe fn decode_bulk<A: Alphabet>(input: &[u8], out: &mut [u8], quads: usize) -> usize {
if quads == 0 || quads * 4 > input.len() || quads * 3 > out.len() {
return 0;
}
unsafe {
let values_low = _mm512_loadu_si512(A::ASCII_LOW.as_ptr().cast());
let values_high = _mm512_loadu_si512(A::ASCII_HIGH.as_ptr().cast());
let gather = _mm512_loadu_si512(GATHER.as_ptr().cast());
let whole = mask_for(BLOCK_GROUPS * 3);
let source = input.as_ptr();
let destination = out.as_mut_ptr();
let mut bad = _mm512_setzero_si512();
let mut done = 0;
macro_rules! convert_loaded {
($chars:expr) => {{
let chars = $chars;
let values = _mm512_permutex2var_epi8(values_low, chars, values_high);
let paired = _mm512_maddubs_epi16(values, _mm512_set1_epi32(0x0140_0140));
let merged = _mm512_madd_epi16(paired, _mm512_set1_epi32(0x0001_1000));
(_mm512_permutexvar_epi8(gather, merged), chars, values)
}};
}
macro_rules! convert {
($at:expr) => {{
let chars = _mm512_loadu_si512(source.add($at * 4).cast());
let values = _mm512_permutex2var_epi8(values_low, chars, values_high);
bad = _mm512_ternarylogic_epi32::<0xFE>(bad, chars, values);
let paired = _mm512_maddubs_epi16(values, _mm512_set1_epi32(0x0140_0140));
let merged = _mm512_madd_epi16(paired, _mm512_set1_epi32(0x0001_1000));
_mm512_permutexvar_epi8(gather, merged)
}};
}
while quads - done >= CHECK_EVERY * BLOCK_GROUPS + 6 {
let mut raw = [_mm512_setzero_si512(); CHECK_EVERY];
for (step, slot) in raw.iter_mut().enumerate() {
*slot = _mm512_loadu_si512(source.add((done + step * BLOCK_GROUPS) * 4).cast());
}
let (first, first_chars, first_values) = convert_loaded!(raw[0]);
let (second, second_chars, second_values) = convert_loaded!(raw[1]);
let (third, third_chars, third_values) = convert_loaded!(raw[2]);
let (fourth, fourth_chars, fourth_values) = convert_loaded!(raw[3]);
_mm512_storeu_si512(destination.add(done * 3).cast(), first);
_mm512_storeu_si512(destination.add((done + BLOCK_GROUPS) * 3).cast(), second);
_mm512_storeu_si512(destination.add((done + 2 * BLOCK_GROUPS) * 3).cast(), third);
_mm512_storeu_si512(
destination.add((done + 3 * BLOCK_GROUPS) * 3).cast(),
fourth,
);
let left = _mm512_ternarylogic_epi32::<0xFE>(first_chars, first_values, second_chars);
let right = _mm512_ternarylogic_epi32::<0xFE>(second_values, third_chars, third_values);
let rest = _mm512_ternarylogic_epi32::<0xFE>(fourth_chars, fourth_values, left);
bad = _mm512_ternarylogic_epi32::<0xFE>(bad, right, rest);
if _mm512_movepi8_mask(bad) != 0 {
return done;
}
done += CHECK_EVERY * BLOCK_GROUPS;
}
while quads - done >= BLOCK_GROUPS + 6 {
let packed = convert!(done);
_mm512_storeu_si512(destination.add(done * 3).cast(), packed);
if _mm512_movepi8_mask(bad) != 0 {
return done;
}
done += BLOCK_GROUPS;
}
while quads - done >= BLOCK_GROUPS {
let packed = convert!(done);
_mm512_mask_storeu_epi8(destination.add(done * 3).cast(), whole, packed);
if _mm512_movepi8_mask(bad) != 0 {
return done;
}
done += BLOCK_GROUPS;
}
if done < quads {
let taking = quads - done;
let read = mask_for(taking * 4);
let chars = _mm512_maskz_loadu_epi8(read, source.add(done * 4).cast());
let values = _mm512_permutex2var_epi8(values_low, chars, values_high);
if (_mm512_movepi8_mask(chars) | _mm512_movepi8_mask(values)) & read != 0 {
return done;
}
let paired = _mm512_maddubs_epi16(values, _mm512_set1_epi32(0x0140_0140));
let merged = _mm512_madd_epi16(paired, _mm512_set1_epi32(0x0001_1000));
let packed = _mm512_permutexvar_epi8(gather, merged);
_mm512_mask_storeu_epi8(
destination.add(done * 3).cast(),
mask_for(taking * 3),
packed,
);
}
quads
}
}