use crate::affine::Affine8;
use crate::bits::Bits;
use crate::isa::{Isa, Portable};
use crate::word::Word;
const fn scratch<L: Lanes>() -> [u8; 64] {
const { assert!(L::LANES <= 64, "Lanes: more than 64 lanes") };
[0; 64]
}
pub trait Lanes: Copy + Eq + core::fmt::Debug {
const LANES: usize;
type Bitmask: Word;
type Isa: Isa;
#[must_use]
fn isa(self) -> Self::Isa;
#[must_use]
fn splat(isa: Self::Isa, b: u8) -> Self;
#[must_use]
fn load(isa: Self::Isa, bytes: &[u8]) -> Self;
#[must_use]
fn lane(self, i: usize) -> u8;
#[must_use]
fn and(self, other: Self) -> Self;
#[must_use]
fn or(self, other: Self) -> Self;
#[must_use]
fn xor(self, other: Self) -> Self;
#[must_use]
fn not(self) -> Self;
#[must_use]
fn add(self, other: Self) -> Self;
#[must_use]
fn sub(self, other: Self) -> Self;
#[must_use]
fn shl(self, n: u32) -> Self;
#[must_use]
fn shr(self, n: u32) -> Self;
#[must_use]
fn cmp_eq(self, other: Self) -> Self;
#[must_use]
fn cmp_le(self, other: Self) -> Self;
#[must_use]
#[doc(alias("pshufb", "tbl"))]
fn lut16(self, table: [u8; 16]) -> Self;
#[must_use]
#[doc(alias("movemask", "pmovmskb"))]
fn to_bitmask(self) -> Self::Bitmask;
#[inline]
#[must_use]
fn zero(isa: Self::Isa) -> Self {
Self::splat(isa, 0)
}
#[inline]
#[must_use]
fn cmp_lt(self, other: Self) -> Self {
self.cmp_le(other).and(self.cmp_eq(other).not())
}
#[inline]
#[must_use]
fn cmp_ge(self, other: Self) -> Self {
other.cmp_le(self)
}
#[inline]
#[must_use]
fn cmp_gt(self, other: Self) -> Self {
other.cmp_lt(self)
}
#[inline]
#[must_use]
fn blend(self, other: Self, mask: Self) -> Self {
mask.and(other).or(mask.not().and(self))
}
#[inline]
#[must_use]
fn min(self, other: Self) -> Self {
other.blend(self, self.cmp_le(other))
}
#[inline]
#[must_use]
fn max(self, other: Self) -> Self {
self.blend(other, self.cmp_le(other))
}
#[inline]
#[must_use]
fn lut16_nibbles(self, lo: [u8; 16], hi: [u8; 16]) -> Self {
self.and(Self::splat(self.isa(), 0x0F))
.lut16(lo)
.and(self.shr(4).lut16(hi))
}
#[inline]
#[must_use]
fn shuffle(self, idx: Self) -> Self {
let mut out = scratch::<Self>();
for (i, o) in out.iter_mut().enumerate().take(Self::LANES) {
let j = idx.lane(i);
*o = if j & 0x80 == 0 {
self.lane(usize::from(j) % Self::LANES)
} else {
0
};
}
Self::load(self.isa(), &out[..Self::LANES])
}
#[inline]
#[must_use]
fn concat_shift(self, other: Self, n: usize) -> Self {
let mut out = scratch::<Self>();
for (i, o) in out.iter_mut().enumerate().take(Self::LANES) {
let j = i + n;
*o = if j < Self::LANES {
self.lane(j)
} else if j < 2 * Self::LANES {
other.lane(j - Self::LANES)
} else {
0
};
}
Self::load(self.isa(), &out[..Self::LANES])
}
#[inline]
#[must_use]
fn add_sat(self, other: Self) -> Self {
self.add(other.min(self.not()))
}
#[inline]
#[must_use]
fn sub_sat(self, other: Self) -> Self {
self.sub(self.min(other))
}
#[inline]
#[must_use]
fn unpack_lo(self, other: Self) -> Self {
let mut out = scratch::<Self>();
for k in 0..Self::LANES / 2 {
out[2 * k] = self.lane(k);
out[2 * k + 1] = other.lane(k);
}
Self::load(self.isa(), &out[..Self::LANES])
}
#[inline]
#[must_use]
fn unpack_hi(self, other: Self) -> Self {
let mut out = scratch::<Self>();
let half = Self::LANES / 2;
for k in 0..half {
out[2 * k] = self.lane(half + k);
out[2 * k + 1] = other.lane(half + k);
}
Self::load(self.isa(), &out[..Self::LANES])
}
#[inline]
#[must_use]
fn sum_abs_diff(self, other: Self) -> u32 {
let d = self.max(other).sub(self.min(other));
(0..Self::LANES).map(|i| u32::from(d.lane(i))).sum()
}
#[inline]
#[must_use]
fn mul_add_pairs(self, weights: Self) -> Self {
let mut out = scratch::<Self>();
for k in 0..Self::LANES / 2 {
let (a0, a1) = (i32::from(self.lane(2 * k)), i32::from(self.lane(2 * k + 1)));
let (w0, w1) = (
i32::from(weights.lane(2 * k).cast_signed()),
i32::from(weights.lane(2 * k + 1).cast_signed()),
);
let s = i16::try_from(a0 * w0 + a1 * w1).unwrap_or(if a0 * w0 + a1 * w1 < 0 {
i16::MIN
} else {
i16::MAX
});
out[2 * k..2 * k + 2].copy_from_slice(&s.to_le_bytes());
}
Self::load(self.isa(), &out[..Self::LANES])
}
#[inline]
#[must_use]
#[doc(alias("gf2p8affineqb", "gfni"))]
fn affine(self, map: Affine8) -> Self {
affine_by_lut16(self, map)
}
#[inline]
#[must_use]
fn reverse_bits(self) -> Self {
self.affine(Affine8::REVERSE)
}
#[inline]
#[must_use]
fn sra(self, n: u32) -> Self {
sra_by_shifts(self, n)
}
#[inline]
#[must_use]
fn rotl(self, n: u32) -> Self {
rotl_by_shifts(self, n)
}
#[inline]
#[must_use]
fn rotr(self, n: u32) -> Self {
self.rotl((8 - n % 8) % 8)
}
#[inline]
#[must_use]
fn avg_round(self, other: Self) -> Self {
self.or(other).sub(self.xor(other).shr(1))
}
#[inline]
#[must_use]
fn avg_floor(self, other: Self) -> Self {
self.and(other).add(self.xor(other).shr(1))
}
#[inline]
#[must_use]
fn ternary(self, b: Self, c: Self, table: u8) -> Self {
let leaf = |t: u8| match t & 3 {
0 => Self::zero(self.isa()),
1 => c.not(),
2 => c,
_ => Self::zero(self.isa()).not(),
};
let on_b = |t: u8| b.and(leaf(t >> 2)).or(b.not().and(leaf(t)));
self.and(on_b(table >> 4)).or(self.not().and(on_b(table)))
}
}
#[inline]
pub(crate) fn affine_by_lut16<L: Lanes>(x: L, map: Affine8) -> L {
let (lo, hi) = map.tables();
x.and(L::splat(x.isa(), 0x0F))
.lut16(lo)
.xor(x.shr(4).lut16(hi))
}
#[inline]
pub(crate) fn sra_by_shifts<L: Lanes>(x: L, n: u32) -> L {
let n = n.min(8);
let sign = x.cmp_ge(L::splat(x.isa(), 0x80));
x.shr(n).or(sign.shl(8 - n))
}
#[inline]
pub(crate) fn rotl_by_shifts<L: Lanes>(x: L, n: u32) -> L {
let n = n % 8;
if n == 0 { x } else { x.shl(n).or(x.shr(8 - n)) }
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct U8x8(u64);
const MSBS_STEP_8: u64 = 0x8080_8080_8080_8080;
const LOW7_STEP_8: u64 = !MSBS_STEP_8;
const GATHER_MSBS_8: u64 = 0x0002_0408_1020_4081;
impl U8x8 {
#[inline]
#[must_use]
pub const fn new(word: u64) -> Self {
Self(word)
}
#[inline]
#[must_use]
pub const fn bits(self) -> u64 {
self.0
}
#[inline]
const fn spread_msbs(msbs: u64) -> u64 {
((msbs & MSBS_STEP_8) >> 7) * 0xFF
}
}
impl U8x8 {
#[inline]
#[must_use]
pub fn splat(b: u8) -> Self {
Self(u64::splat_byte(b))
}
#[inline]
#[must_use]
pub const fn load(bytes: &[u8]) -> Self {
let mut w = [0; 8];
w.copy_from_slice(bytes);
Self(u64::from_le_bytes(w))
}
#[inline]
#[must_use]
pub const fn zero() -> Self {
Self(0)
}
}
impl Lanes for U8x8 {
const LANES: usize = 8;
type Bitmask = u8;
type Isa = Portable;
#[inline]
fn isa(self) -> Portable {
Portable
}
#[inline]
fn splat(_: Portable, b: u8) -> Self {
Self::splat(b)
}
#[inline]
fn load(_: Portable, bytes: &[u8]) -> Self {
Self::load(bytes)
}
#[inline]
fn lane(self, i: usize) -> u8 {
self.0.to_le_bytes()[i]
}
#[inline]
fn and(self, other: Self) -> Self {
Self(self.0 & other.0)
}
#[inline]
fn or(self, other: Self) -> Self {
Self(self.0 | other.0)
}
#[inline]
fn xor(self, other: Self) -> Self {
Self(self.0 ^ other.0)
}
#[inline]
fn not(self) -> Self {
Self(!self.0)
}
#[inline]
fn add(self, other: Self) -> Self {
let low = (self.0 & LOW7_STEP_8) + (other.0 & LOW7_STEP_8);
Self(low ^ ((self.0 ^ other.0) & MSBS_STEP_8))
}
#[inline]
fn sub(self, other: Self) -> Self {
let low = (self.0 | MSBS_STEP_8) - (other.0 & LOW7_STEP_8);
Self(low ^ ((self.0 ^ !other.0) & MSBS_STEP_8))
}
#[inline]
fn shl(self, n: u32) -> Self {
if n >= 8 {
return Self::zero();
}
Self((self.0 << n) & u64::splat_byte(0xFFu8 << n))
}
#[inline]
fn shr(self, n: u32) -> Self {
if n >= 8 {
return Self::zero();
}
Self((self.0 >> n) & u64::splat_byte(0xFF >> n))
}
#[inline]
fn cmp_eq(self, other: Self) -> Self {
Self(Self::spread_msbs((self.0 ^ other.0).zero_bytes()))
}
#[inline]
fn cmp_le(self, other: Self) -> Self {
let (x, y) = (self.0, other.0);
let le = (((y | MSBS_STEP_8) - (x & LOW7_STEP_8)) | (x ^ y)) ^ (x & !y);
Self(Self::spread_msbs(le))
}
#[inline]
fn lut16(self, table: [u8; 16]) -> Self {
let mut out = [0u8; 8];
for (o, lane) in out.iter_mut().zip(self.0.to_le_bytes()) {
*o = if lane & 0x80 == 0 {
table[usize::from(lane & 0x0F)]
} else {
0
};
}
Self(u64::from_le_bytes(out))
}
#[inline]
fn to_bitmask(self) -> u8 {
#[allow(clippy::cast_possible_truncation)]
let gathered = ((self.0 & MSBS_STEP_8).wrapping_mul(GATHER_MSBS_8) >> 56) as u8;
gathered
}
#[inline]
fn affine(self, map: Affine8) -> Self {
Self(map.apply8(self.0))
}
#[inline]
fn reverse_bits(self) -> Self {
Self(self.0.reverse_bits().swap_bytes())
}
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
#[allow(unsafe_code)]
mod x86 {
use core::arch::x86_64::{
__m128i, _mm_add_epi8, _mm_adds_epu8, _mm_alignr_epi8, _mm_and_si128, _mm_avg_epu8,
_mm_cmpeq_epi8, _mm_cvtsi32_si128, _mm_cvtsi128_si64, _mm_gf2p8affine_epi64_epi8,
_mm_maddubs_epi16, _mm_min_epu8, _mm_movemask_epi8, _mm_or_si128, _mm_sad_epu8,
_mm_set_epi64x, _mm_set1_epi8, _mm_set1_epi64x, _mm_shuffle_epi8, _mm_sll_epi16,
_mm_srl_epi16, _mm_srli_si128, _mm_sub_epi8, _mm_subs_epu8, _mm_unpackhi_epi8,
_mm_unpacklo_epi8, _mm_xor_si128,
};
use core::marker::PhantomData;
use super::{Affine8, Lanes, U8x8, affine_by_lut16, rotl_by_shifts, sra_by_shifts};
use crate::isa::X86Level;
#[derive(Clone, Copy)]
pub struct X86x16<L>(__m128i, PhantomData<L>);
impl<L: X86Level> X86x16<L> {
#[inline]
const fn r(v: __m128i) -> Self {
Self(v, PhantomData)
}
#[inline]
#[must_use]
pub fn from_halves_in(isa: L, lo: u64, hi: u64) -> Self {
let _ = isa;
Self::r(unsafe { _mm_set_epi64x(hi.cast_signed(), lo.cast_signed()) })
}
#[inline]
#[must_use]
pub fn halves(self) -> (u64, u64) {
let (lo, hi) = unsafe {
(
_mm_cvtsi128_si64(self.0),
_mm_cvtsi128_si64(_mm_srli_si128::<8>(self.0)),
)
};
(lo.cast_unsigned(), hi.cast_unsigned())
}
#[inline]
fn shift_by_words(self, n: u32, left: bool) -> Self {
if n >= 8 {
return Self::zero(self.isa());
}
let count = unsafe { _mm_cvtsi32_si128(n.cast_signed()) };
if left {
Self::r(unsafe { _mm_sll_epi16(self.0, count) })
.and(Self::splat(self.isa(), 0xFFu8 << n))
} else {
Self::r(unsafe { _mm_srl_epi16(self.0, count) })
.and(Self::splat(self.isa(), 0xFF >> n))
}
}
}
impl<L: X86Level> Lanes for X86x16<L> {
const LANES: usize = 16;
type Bitmask = u16;
type Isa = L;
#[inline]
fn isa(self) -> L {
unsafe { L::assume() }
}
#[inline]
fn splat(isa: L, b: u8) -> Self {
let _ = isa;
Self::r(unsafe { _mm_set1_epi8(b.cast_signed()) })
}
#[inline]
fn load(isa: L, bytes: &[u8]) -> Self {
assert!(
bytes.len() == 16,
"U8x16::load needs 16 bytes, got {}",
bytes.len()
);
let (lo, hi) = (U8x8::load(&bytes[..8]), U8x8::load(&bytes[8..]));
Self::from_halves_in(isa, lo.bits(), hi.bits())
}
#[inline]
fn lane(self, i: usize) -> u8 {
let (lo, hi) = self.halves();
if i < 8 {
U8x8::new(lo).lane(i)
} else {
U8x8::new(hi).lane(i - 8)
}
}
#[inline]
fn and(self, other: Self) -> Self {
Self::r(unsafe { _mm_and_si128(self.0, other.0) })
}
#[inline]
fn or(self, other: Self) -> Self {
Self::r(unsafe { _mm_or_si128(self.0, other.0) })
}
#[inline]
fn xor(self, other: Self) -> Self {
Self::r(unsafe { _mm_xor_si128(self.0, other.0) })
}
#[inline]
fn not(self) -> Self {
Self::r(unsafe { _mm_xor_si128(self.0, _mm_set1_epi8(-1)) })
}
#[inline]
fn add(self, other: Self) -> Self {
Self::r(unsafe { _mm_add_epi8(self.0, other.0) })
}
#[inline]
fn sub(self, other: Self) -> Self {
Self::r(unsafe { _mm_sub_epi8(self.0, other.0) })
}
#[inline]
fn shl(self, n: u32) -> Self {
if L::GFNI {
self.affine(Affine8::shl(n))
} else {
self.shift_by_words(n, true)
}
}
#[inline]
fn shr(self, n: u32) -> Self {
if L::GFNI {
self.affine(Affine8::shr(n))
} else {
self.shift_by_words(n, false)
}
}
#[inline]
fn cmp_eq(self, other: Self) -> Self {
Self::r(unsafe { _mm_cmpeq_epi8(self.0, other.0) })
}
#[inline]
fn cmp_le(self, other: Self) -> Self {
Self::r(unsafe { _mm_cmpeq_epi8(_mm_min_epu8(self.0, other.0), self.0) })
}
#[inline]
fn lut16(self, table: [u8; 16]) -> Self {
Self::r(unsafe { _mm_shuffle_epi8(Self::load(self.isa(), &table).0, self.0) })
}
#[inline]
fn to_bitmask(self) -> u16 {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let bits = unsafe { _mm_movemask_epi8(self.0) } as u16;
bits
}
#[inline]
fn shuffle(self, idx: Self) -> Self {
Self::r(unsafe { _mm_shuffle_epi8(self.0, idx.0) })
}
#[inline]
fn concat_shift(self, other: Self, n: usize) -> Self {
macro_rules! alignr {
($($k:literal),*) => {
match n {
$($k => Self::r(unsafe { _mm_alignr_epi8::<$k>(other.0, self.0) }),)*
17..=31 => other.concat_shift(Self::zero(self.isa()), n - 16),
_ => Self::zero(self.isa()),
}
};
}
alignr!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
}
#[inline]
fn add_sat(self, other: Self) -> Self {
Self::r(unsafe { _mm_adds_epu8(self.0, other.0) })
}
#[inline]
fn sub_sat(self, other: Self) -> Self {
Self::r(unsafe { _mm_subs_epu8(self.0, other.0) })
}
#[inline]
fn unpack_lo(self, other: Self) -> Self {
Self::r(unsafe { _mm_unpacklo_epi8(self.0, other.0) })
}
#[inline]
fn unpack_hi(self, other: Self) -> Self {
Self::r(unsafe { _mm_unpackhi_epi8(self.0, other.0) })
}
#[inline]
fn sum_abs_diff(self, other: Self) -> u32 {
let (lo, hi) = Self::r(unsafe { _mm_sad_epu8(self.0, other.0) }).halves();
#[allow(clippy::cast_possible_truncation)]
let sum = (lo + hi) as u32;
sum
}
#[inline]
fn mul_add_pairs(self, weights: Self) -> Self {
Self::r(unsafe { _mm_maddubs_epi16(self.0, weights.0) })
}
#[inline]
fn avg_round(self, other: Self) -> Self {
Self::r(unsafe { _mm_avg_epu8(self.0, other.0) })
}
#[inline]
fn affine(self, map: Affine8) -> Self {
if !L::GFNI {
return affine_by_lut16(self, map);
}
let linear = Self::r(unsafe {
_mm_gf2p8affine_epi64_epi8::<0>(self.0, _mm_set1_epi64x(map.matrix().cast_signed()))
});
if map.is_linear() {
linear
} else {
linear.xor(Self::splat(self.isa(), map.add()))
}
}
#[inline]
fn sra(self, n: u32) -> Self {
if L::GFNI {
self.affine(Affine8::sra(n))
} else {
sra_by_shifts(self, n)
}
}
#[inline]
fn rotl(self, n: u32) -> Self {
if L::GFNI {
self.affine(Affine8::rotl(n))
} else {
rotl_by_shifts(self, n)
}
}
#[inline]
fn rotr(self, n: u32) -> Self {
if L::GFNI {
self.affine(Affine8::rotr(n))
} else {
rotl_by_shifts(self, (8 - n % 8) % 8)
}
}
}
#[cfg(all(target_feature = "ssse3", not(feature = "portable")))]
impl X86x16<crate::isa::Native> {
#[inline]
#[must_use]
pub fn splat(b: u8) -> Self {
<Self as Lanes>::splat(crate::isa::Native, b)
}
#[inline]
#[must_use]
pub fn load(bytes: &[u8]) -> Self {
<Self as Lanes>::load(crate::isa::Native, bytes)
}
#[inline]
#[must_use]
pub fn zero() -> Self {
<Self as Lanes>::zero(crate::isa::Native)
}
#[inline]
#[must_use]
pub fn from_halves(lo: u64, hi: u64) -> Self {
Self::from_halves_in(crate::isa::Native, lo, hi)
}
}
}
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
pub use x86::X86x16;
#[cfg(all(
target_arch = "aarch64",
target_feature = "neon",
not(feature = "portable")
))]
#[allow(unsafe_code)]
mod neon {
use core::arch::aarch64::{
uint8x16_t, uint8x16x2_t, vabdq_u8, vaddlvq_u8, vaddq_u8, vandq_u8, vceqq_u8, vcleq_u8,
vcltzq_s8, vcombine_s16, vcombine_u8, vcreate_u8, vdupq_n_s8, vdupq_n_u8, veorq_u8,
vget_high_s8, vget_high_u8, vget_lane_u64, vget_low_s8, vget_low_s16, vget_low_u8,
vgetq_lane_u64, vhaddq_u8, vmovl_s8, vmovl_u8, vmull_high_s16, vmull_s16, vmvnq_u8,
vorrq_u8, vpaddq_s32, vqaddq_u8, vqmovn_s32, vqsubq_u8, vqtbl1q_u8, vqtbl2q_u8, vrbitq_u8,
vreinterpret_u64_u8, vreinterpretq_s8_u8, vreinterpretq_s16_u16, vreinterpretq_u8_s8,
vreinterpretq_u8_s16, vreinterpretq_u16_u8, vreinterpretq_u64_u8, vrhaddq_u8, vshlq_s8,
vshlq_u8, vshrn_n_u16, vsubq_u8, vzip1q_u8, vzip2q_u8,
};
use super::{Lanes, U8x8};
use crate::isa::Native;
#[derive(Clone, Copy)]
pub struct Neon16(uint8x16_t);
impl Neon16 {
#[inline]
#[must_use]
pub fn from_halves(lo: u64, hi: u64) -> Self {
Self(unsafe { vcombine_u8(vcreate_u8(lo), vcreate_u8(hi)) })
}
#[inline]
#[must_use]
pub fn halves(self) -> (u64, u64) {
unsafe {
let v = vreinterpretq_u64_u8(self.0);
(vgetq_lane_u64::<0>(v), vgetq_lane_u64::<1>(v))
}
}
}
impl Neon16 {
#[inline]
#[must_use]
pub fn splat(b: u8) -> Self {
<Self as Lanes>::splat(Native, b)
}
#[inline]
#[must_use]
pub fn load(bytes: &[u8]) -> Self {
<Self as Lanes>::load(Native, bytes)
}
#[inline]
#[must_use]
pub fn zero() -> Self {
<Self as Lanes>::zero(Native)
}
}
impl Lanes for Neon16 {
const LANES: usize = 16;
type Bitmask = u16;
type Isa = Native;
#[inline]
fn isa(self) -> Native {
Native
}
#[inline]
fn splat(_: Native, b: u8) -> Self {
Self(unsafe { vdupq_n_u8(b) })
}
#[inline]
fn load(_: Native, bytes: &[u8]) -> Self {
assert!(
bytes.len() == 16,
"U8x16::load needs 16 bytes, got {}",
bytes.len()
);
let (lo, hi) = (U8x8::load(&bytes[..8]), U8x8::load(&bytes[8..]));
Self::from_halves(lo.bits(), hi.bits())
}
#[inline]
fn lane(self, i: usize) -> u8 {
let (lo, hi) = self.halves();
if i < 8 {
U8x8::new(lo).lane(i)
} else {
U8x8::new(hi).lane(i - 8)
}
}
#[inline]
fn and(self, other: Self) -> Self {
Self(unsafe { vandq_u8(self.0, other.0) })
}
#[inline]
fn or(self, other: Self) -> Self {
Self(unsafe { vorrq_u8(self.0, other.0) })
}
#[inline]
fn xor(self, other: Self) -> Self {
Self(unsafe { veorq_u8(self.0, other.0) })
}
#[inline]
fn not(self) -> Self {
Self(unsafe { vmvnq_u8(self.0) })
}
#[inline]
fn add(self, other: Self) -> Self {
Self(unsafe { vaddq_u8(self.0, other.0) })
}
#[inline]
fn sub(self, other: Self) -> Self {
Self(unsafe { vsubq_u8(self.0, other.0) })
}
#[inline]
fn shl(self, n: u32) -> Self {
if n >= 8 {
return Self::zero();
}
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let count = n as i8;
Self(unsafe { vshlq_u8(self.0, vdupq_n_s8(count)) })
}
#[inline]
fn shr(self, n: u32) -> Self {
if n >= 8 {
return Self::zero();
}
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let count = -(n as i8);
Self(unsafe { vshlq_u8(self.0, vdupq_n_s8(count)) })
}
#[inline]
fn cmp_eq(self, other: Self) -> Self {
Self(unsafe { vceqq_u8(self.0, other.0) })
}
#[inline]
fn cmp_le(self, other: Self) -> Self {
Self(unsafe { vcleq_u8(self.0, other.0) })
}
#[inline]
fn lut16(self, table: [u8; 16]) -> Self {
Self(unsafe { vqtbl1q_u8(Self::load(&table).0, vandq_u8(self.0, vdupq_n_u8(0x8F))) })
}
#[inline]
fn to_bitmask(self) -> u16 {
let mut x = unsafe {
let full = vcltzq_s8(vreinterpretq_s8_u8(self.0));
let nibbles = vshrn_n_u16::<4>(vreinterpretq_u16_u8(full));
vget_lane_u64::<0>(vreinterpret_u64_u8(nibbles))
} & 0x1111_1111_1111_1111;
x = (x | x >> 3) & 0x3333_3333_3333_3333;
x = (x | x >> 6) & 0x0F0F_0F0F_0F0F_0F0F;
x = (x | x >> 12) & 0x00FF_00FF_00FF_00FF;
x = (x | x >> 24) & 0x0000_FFFF_0000_FFFF;
x |= x >> 48;
#[allow(clippy::cast_possible_truncation)]
let bits = x as u16;
bits
}
#[inline]
fn shuffle(self, idx: Self) -> Self {
Self(unsafe { vqtbl1q_u8(self.0, vandq_u8(idx.0, vdupq_n_u8(0x8F))) })
}
#[inline]
fn concat_shift(self, other: Self, n: usize) -> Self {
if n >= 32 {
return Self::zero();
}
#[allow(clippy::cast_possible_truncation)]
let start = n as u8;
let iota = Self::load(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
Self(unsafe {
vqtbl2q_u8(
uint8x16x2_t(self.0, other.0),
vaddq_u8(iota.0, vdupq_n_u8(start)),
)
})
}
#[inline]
fn add_sat(self, other: Self) -> Self {
Self(unsafe { vqaddq_u8(self.0, other.0) })
}
#[inline]
fn sub_sat(self, other: Self) -> Self {
Self(unsafe { vqsubq_u8(self.0, other.0) })
}
#[inline]
fn unpack_lo(self, other: Self) -> Self {
Self(unsafe { vzip1q_u8(self.0, other.0) })
}
#[inline]
fn unpack_hi(self, other: Self) -> Self {
Self(unsafe { vzip2q_u8(self.0, other.0) })
}
#[inline]
fn sum_abs_diff(self, other: Self) -> u32 {
u32::from(unsafe { vaddlvq_u8(vabdq_u8(self.0, other.0)) })
}
#[inline]
fn mul_add_pairs(self, weights: Self) -> Self {
Self(unsafe {
let a_lo = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(self.0)));
let a_hi = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(self.0)));
let w = vreinterpretq_s8_u8(weights.0);
let w_lo = vmovl_s8(vget_low_s8(w));
let w_hi = vmovl_s8(vget_high_s8(w));
let p0 = vmull_s16(vget_low_s16(a_lo), vget_low_s16(w_lo));
let p1 = vmull_high_s16(a_lo, w_lo);
let p2 = vmull_s16(vget_low_s16(a_hi), vget_low_s16(w_hi));
let p3 = vmull_high_s16(a_hi, w_hi);
let sums = vcombine_s16(
vqmovn_s32(vpaddq_s32(p0, p1)),
vqmovn_s32(vpaddq_s32(p2, p3)),
);
vreinterpretq_u8_s16(sums)
})
}
#[inline]
fn reverse_bits(self) -> Self {
Self(unsafe { vrbitq_u8(self.0) })
}
#[inline]
fn sra(self, n: u32) -> Self {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
let count = -(n.min(8) as i8);
Self(unsafe {
vreinterpretq_u8_s8(vshlq_s8(vreinterpretq_s8_u8(self.0), vdupq_n_s8(count)))
})
}
#[inline]
fn avg_round(self, other: Self) -> Self {
Self(unsafe { vrhaddq_u8(self.0, other.0) })
}
#[inline]
fn avg_floor(self, other: Self) -> Self {
Self(unsafe { vhaddq_u8(self.0, other.0) })
}
}
}
#[cfg(all(
target_arch = "aarch64",
target_feature = "neon",
not(feature = "portable")
))]
pub use neon::Neon16;
mod swar {
use core::marker::PhantomData;
use super::{Affine8, Lanes, U8x8};
use crate::isa::{Isa, Portable};
#[derive(Clone, Copy)]
pub struct Swar16<I = Portable>([U8x8; 2], PhantomData<I>);
impl<I: Isa + Default> Swar16<I> {
#[inline]
const fn r(halves: [U8x8; 2]) -> Self {
Self(halves, PhantomData)
}
#[allow(clippy::missing_const_for_fn)]
#[inline]
#[must_use]
pub fn from_halves(lo: u64, hi: u64) -> Self {
Self::r([U8x8::new(lo), U8x8::new(hi)])
}
#[allow(clippy::missing_const_for_fn)]
#[inline]
#[must_use]
pub fn halves(self) -> (u64, u64) {
(self.0[0].bits(), self.0[1].bits())
}
#[inline]
#[must_use]
pub fn splat(b: u8) -> Self {
Self::r([U8x8::splat(b), U8x8::splat(b)])
}
#[inline]
#[must_use]
pub fn load(bytes: &[u8]) -> Self {
assert!(
bytes.len() == 16,
"U8x16::load needs 16 bytes, got {}",
bytes.len()
);
Self::r([U8x8::load(&bytes[..8]), U8x8::load(&bytes[8..])])
}
#[inline]
#[must_use]
pub fn zero() -> Self {
Self::splat(0)
}
#[inline]
fn map(self, other: Self, f: impl Fn(U8x8, U8x8) -> U8x8) -> Self {
Self::r([f(self.0[0], other.0[0]), f(self.0[1], other.0[1])])
}
#[inline]
fn each(self, f: impl Fn(U8x8) -> U8x8) -> Self {
Self::r([f(self.0[0]), f(self.0[1])])
}
}
impl<I: Isa + Default> Lanes for Swar16<I> {
const LANES: usize = 16;
type Bitmask = u16;
type Isa = I;
#[inline]
fn isa(self) -> I {
I::default()
}
#[inline]
fn splat(_: I, b: u8) -> Self {
Self::splat(b)
}
#[inline]
fn load(_: I, bytes: &[u8]) -> Self {
Self::load(bytes)
}
#[inline]
fn lane(self, i: usize) -> u8 {
if i < 8 {
self.0[0].lane(i)
} else {
self.0[1].lane(i - 8)
}
}
#[inline]
fn and(self, other: Self) -> Self {
self.map(other, U8x8::and)
}
#[inline]
fn or(self, other: Self) -> Self {
self.map(other, U8x8::or)
}
#[inline]
fn xor(self, other: Self) -> Self {
self.map(other, U8x8::xor)
}
#[inline]
fn not(self) -> Self {
self.each(U8x8::not)
}
#[inline]
fn add(self, other: Self) -> Self {
self.map(other, U8x8::add)
}
#[inline]
fn sub(self, other: Self) -> Self {
self.map(other, U8x8::sub)
}
#[inline]
fn shl(self, n: u32) -> Self {
self.each(|h| h.shl(n))
}
#[inline]
fn shr(self, n: u32) -> Self {
self.each(|h| h.shr(n))
}
#[inline]
fn cmp_eq(self, other: Self) -> Self {
self.map(other, U8x8::cmp_eq)
}
#[inline]
fn cmp_le(self, other: Self) -> Self {
self.map(other, U8x8::cmp_le)
}
#[inline]
fn lut16(self, table: [u8; 16]) -> Self {
self.each(|h| h.lut16(table))
}
#[inline]
fn to_bitmask(self) -> u16 {
u16::from(self.0[0].to_bitmask()) | u16::from(self.0[1].to_bitmask()) << 8
}
#[inline]
fn affine(self, map: Affine8) -> Self {
self.each(|h| h.affine(map))
}
#[inline]
fn reverse_bits(self) -> Self {
self.each(U8x8::reverse_bits)
}
}
}
pub use swar::Swar16;
#[cfg(all(
target_arch = "x86_64",
target_feature = "ssse3",
not(feature = "portable")
))]
pub type NativeU8x16 = X86x16<crate::isa::Native>;
#[cfg(all(
target_arch = "aarch64",
target_feature = "neon",
not(feature = "portable")
))]
pub type NativeU8x16 = Neon16;
#[cfg(not(any(
all(
target_arch = "x86_64",
target_feature = "ssse3",
not(feature = "portable")
),
all(
target_arch = "aarch64",
target_feature = "neon",
not(feature = "portable")
)
)))]
pub type NativeU8x16 = Swar16<crate::isa::Native>;
pub type U8x16 = NativeU8x16;
macro_rules! halves_eq_debug {
($([$($g:tt)*] $t:ty),*) => {$(
impl<$($g)*> PartialEq for $t {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.halves() == other.halves()
}
}
impl<$($g)*> Eq for $t {}
impl<$($g)*> core::fmt::Debug for $t {
#[allow(clippy::missing_inline_in_public_items)]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let (lo, hi) = self.halves();
let mut bytes = [0u8; 16];
bytes[..8].copy_from_slice(&lo.to_le_bytes());
bytes[8..].copy_from_slice(&hi.to_le_bytes());
f.debug_tuple("U8x16").field(&bytes).finish()
}
}
)*};
}
halves_eq_debug!([I: Isa + Default] Swar16<I>);
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
halves_eq_debug!([L: crate::isa::X86Level] X86x16<L>);
#[cfg(all(
target_arch = "aarch64",
target_feature = "neon",
not(feature = "portable")
))]
halves_eq_debug!([] Neon16);