use crate::prefilter::algo::can_overread;
use crate::smith_waterman::algo::{ascii_gap, unicode_gap};
use std::arch::aarch64::*;
use super::{Backend, BytesVec, MaskVec, ScoreVec};
#[derive(Debug, Clone, Copy)]
pub struct BackendNEON;
#[derive(Debug, Clone, Copy)]
pub struct NeonBytes(uint8x8_t);
#[derive(Debug, Clone, Copy)]
pub struct NeonScore(uint16x8_t);
impl Backend for BackendNEON {
const LANES: usize = 8;
const LANE_BYTES: usize = 2;
type Bytes = NeonBytes;
type Mask = NeonBytes;
type Score = NeonScore;
fn is_available() -> bool {
true
}
#[inline(always)]
unsafe fn widen_mask(m: Self::Mask) -> Self::Score {
unsafe { NeonScore(vreinterpretq_u16_s16(vmovl_s8(vreinterpret_s8_u8(m.0)))) }
}
#[inline(always)]
unsafe fn propagate_horizontal_gaps(
row: Self::Score,
adjacent_row: Self::Score,
match_mask: Self::Score,
adjacent_match_mask: Self::Score,
gap_open_penalty: Self::Score,
gap_extend_penalty: Self::Score,
) -> Self::Score {
unsafe {
ascii_gap::propagate_8_lane::<BackendNEON>(
row,
adjacent_row,
match_mask,
adjacent_match_mask,
gap_open_penalty,
gap_extend_penalty,
)
}
}
#[inline(always)]
unsafe fn propagate_horizontal_unicode_gaps(
row: Self::Score,
adjacent_row: Self::Score,
pending_gap_open_mask: Self::Score,
adjacent_pending_gap_open_mask: Self::Score,
continuation_gap_extend_penalty: Self::Score,
adjacent_continuation_gap_extend_penalty: Self::Score,
scalar_end_mask: Self::Score,
adjacent_scalar_end_mask: Self::Score,
gap_open_penalty: Self::Score,
gap_extend_penalty: Self::Score,
) -> (Self::Score, Self::Score) {
unsafe {
unicode_gap::propagate_unicode_8_lane::<BackendNEON>(
row,
adjacent_row,
pending_gap_open_mask,
adjacent_pending_gap_open_mask,
continuation_gap_extend_penalty,
adjacent_continuation_gap_extend_penalty,
scalar_end_mask,
adjacent_scalar_end_mask,
gap_open_penalty,
gap_extend_penalty,
)
}
}
}
impl NeonBytes {
#[inline(always)]
unsafe fn load_partial_safe(ptr: *const u8, len: usize) -> uint8x8_t {
unsafe {
debug_assert!(len < 8);
let val: u64 = match len {
0 => 0,
1 => *ptr as u64,
2 => (ptr as *const u16).read_unaligned() as u64,
3 => {
let lo = (ptr as *const u16).read_unaligned() as u64;
let hi = *ptr.add(2) as u64;
lo | (hi << 16)
}
4 => (ptr as *const u32).read_unaligned() as u64,
5 => {
let lo = (ptr as *const u32).read_unaligned() as u64;
let hi = *ptr.add(4) as u64;
lo | (hi << 32)
}
6 => {
let lo = (ptr as *const u32).read_unaligned() as u64;
let hi = (ptr.add(4) as *const u16).read_unaligned() as u64;
lo | (hi << 32)
}
7 => {
let lo = (ptr as *const u32).read_unaligned() as u64;
let mid = (ptr.add(4) as *const u16).read_unaligned() as u64;
let hi = *ptr.add(6) as u64;
lo | (mid << 32) | (hi << 48)
}
_ => std::hint::unreachable_unchecked(),
};
vreinterpret_u8_u64(vdup_n_u64(val))
}
}
}
impl BytesVec for NeonBytes {
type Mask = NeonBytes;
#[inline(always)]
unsafe fn splat(value: u8) -> Self {
unsafe { Self(vdup_n_u8(value)) }
}
#[inline(always)]
unsafe fn eq(self, other: Self) -> Self::Mask {
unsafe { Self(vceq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn gt(self, other: Self) -> Self::Mask {
unsafe { Self(vcgt_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn lt(self, other: Self) -> Self::Mask {
unsafe { Self(vclt_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn load_partial(data: *const u8, start: usize, len: usize) -> Self {
unsafe {
let remaining = len.saturating_sub(start);
if remaining == 0 {
return Self(vdup_n_u8(0));
}
let ptr = data.add(start);
Self(match remaining {
8.. => vld1_u8(ptr),
1..=7 if can_overread(ptr, 8) => {
let lo = vld1_u8(ptr);
let mask_bytes: [u8; 8] = [
if 0 < remaining { 0xFF } else { 0 },
if 1 < remaining { 0xFF } else { 0 },
if 2 < remaining { 0xFF } else { 0 },
if 3 < remaining { 0xFF } else { 0 },
if 4 < remaining { 0xFF } else { 0 },
if 5 < remaining { 0xFF } else { 0 },
if 6 < remaining { 0xFF } else { 0 },
if 7 < remaining { 0xFF } else { 0 },
];
vand_u8(lo, vld1_u8(mask_bytes.as_ptr()))
}
_ => Self::load_partial_safe(ptr, remaining),
})
}
}
#[cfg(test)]
fn from_lanes(values: &[u8]) -> Self {
assert_eq!(values.len(), 8);
Self(unsafe { vld1_u8(values.as_ptr()) })
}
#[cfg(test)]
fn to_lanes(self) -> Vec<u8> {
let mut buf = [0u8; 8];
unsafe { vst1_u8(buf.as_mut_ptr(), self.0) };
buf.to_vec()
}
}
impl MaskVec for NeonBytes {
#[inline(always)]
unsafe fn zero() -> Self {
unsafe { Self(vdup_n_u8(0)) }
}
#[inline(always)]
unsafe fn and(self, other: Self) -> Self {
unsafe { Self(vand_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn or(self, other: Self) -> Self {
unsafe { Self(vorr_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn not(self) -> Self {
unsafe { Self(vmvn_u8(self.0)) }
}
#[inline(always)]
unsafe fn is_zero(self) -> bool {
unsafe { vmaxv_u8(self.0) == 0 }
}
#[inline(always)]
unsafe fn shift_right_padded_1(self, prev: Self) -> Self {
unsafe { Self(vext_u8::<7>(prev.0, self.0)) }
}
#[cfg(test)]
fn from_lanes(values: &[bool]) -> Self {
assert_eq!(values.len(), 8);
let mut buf = [0u8; 8];
for i in 0..8 {
buf[i] = if values[i] { 0xFF } else { 0 };
}
Self(unsafe { vld1_u8(buf.as_ptr()) })
}
#[cfg(test)]
fn to_lanes(self) -> Vec<bool> {
let mut buf = [0u8; 8];
unsafe { vst1_u8(buf.as_mut_ptr(), self.0) };
buf.iter().map(|&v| v != 0).collect()
}
}
impl ScoreVec for NeonScore {
#[inline(always)]
unsafe fn zero() -> Self {
unsafe { Self(vdupq_n_u16(0)) }
}
#[inline(always)]
unsafe fn splat(value: u16) -> Self {
unsafe { Self(vdupq_n_u16(value)) }
}
#[inline(always)]
unsafe fn first_lane(value: u16) -> Self {
unsafe { Self(vsetq_lane_u16::<0>(value, vdupq_n_u16(0))) }
}
#[inline(always)]
unsafe fn max(self, other: Self) -> Self {
unsafe { Self(vmaxq_u16(self.0, other.0)) }
}
#[inline(always)]
unsafe fn horizontal_max(self) -> u16 {
unsafe { vmaxvq_u16(self.0) }
}
#[inline(always)]
unsafe fn add(self, other: Self) -> Self {
unsafe { Self(vaddq_u16(self.0, other.0)) }
}
#[inline(always)]
unsafe fn subs(self, other: Self) -> Self {
unsafe { Self(vqsubq_u16(self.0, other.0)) }
}
#[inline(always)]
unsafe fn and(self, other: Self) -> Self {
unsafe { Self(vandq_u16(self.0, other.0)) }
}
#[inline(always)]
unsafe fn shift_right_padded<const L: i32>(self, prev: Self) -> Self {
unsafe {
const { assert!(L >= 0 && L <= 8) };
Self(match L {
0 => self.0,
1 => vextq_u16::<7>(prev.0, self.0),
2 => vextq_u16::<6>(prev.0, self.0),
3 => vextq_u16::<5>(prev.0, self.0),
4 => vextq_u16::<4>(prev.0, self.0),
5 => vextq_u16::<3>(prev.0, self.0),
6 => vextq_u16::<2>(prev.0, self.0),
7 => vextq_u16::<1>(prev.0, self.0),
8 => prev.0,
_ => std::hint::unreachable_unchecked(),
})
}
}
#[inline(always)]
unsafe fn find_lane(self, search: u16) -> usize {
unsafe {
let cmp = vceqq_u16(self.0, vdupq_n_u16(search));
let narrowed = vmovn_u16(cmp);
let bits = vget_lane_u64::<0>(vreinterpret_u64_u8(narrowed));
if bits == 0 {
8
} else {
bits.trailing_zeros() as usize / 8
}
}
}
#[cfg(test)]
fn from_lanes(values: &[u16]) -> Self {
assert_eq!(values.len(), 8);
Self(unsafe { vld1q_u16(values.as_ptr()) })
}
#[cfg(test)]
fn to_lanes(self) -> Vec<u16> {
let mut buf = [0u16; 8];
unsafe { vst1q_u16(buf.as_mut_ptr(), self.0) };
buf.to_vec()
}
}
#[derive(Debug, Clone, Copy)]
pub struct BackendNEONU8;
#[derive(Debug, Clone, Copy)]
pub struct NeonU8Bytes(uint8x16_t);
#[derive(Debug, Clone, Copy)]
pub struct NeonU8Score(uint8x16_t);
impl Backend for BackendNEONU8 {
const LANES: usize = 16;
const LANE_BYTES: usize = 1;
type Bytes = NeonU8Bytes;
type Mask = NeonU8Bytes;
type Score = NeonU8Score;
fn is_available() -> bool {
true
}
#[inline(always)]
unsafe fn widen_mask(m: Self::Mask) -> Self::Score {
NeonU8Score(m.0)
}
#[inline(always)]
unsafe fn propagate_horizontal_gaps(
row: Self::Score,
adjacent_row: Self::Score,
match_mask: Self::Score,
adjacent_match_mask: Self::Score,
gap_open_penalty: Self::Score,
gap_extend_penalty: Self::Score,
) -> Self::Score {
unsafe {
ascii_gap::propagate_16_lane::<BackendNEONU8>(
row,
adjacent_row,
match_mask,
adjacent_match_mask,
gap_open_penalty,
gap_extend_penalty,
)
}
}
#[inline(always)]
unsafe fn propagate_horizontal_unicode_gaps(
row: Self::Score,
adjacent_row: Self::Score,
pending_gap_open_mask: Self::Score,
adjacent_pending_gap_open_mask: Self::Score,
continuation_gap_extend_penalty: Self::Score,
adjacent_continuation_gap_extend_penalty: Self::Score,
scalar_end_mask: Self::Score,
adjacent_scalar_end_mask: Self::Score,
gap_open_penalty: Self::Score,
gap_extend_penalty: Self::Score,
) -> (Self::Score, Self::Score) {
unsafe {
unicode_gap::propagate_unicode_16_lane::<BackendNEONU8>(
row,
adjacent_row,
pending_gap_open_mask,
adjacent_pending_gap_open_mask,
continuation_gap_extend_penalty,
adjacent_continuation_gap_extend_penalty,
scalar_end_mask,
adjacent_scalar_end_mask,
gap_open_penalty,
gap_extend_penalty,
)
}
}
}
impl NeonU8Bytes {
#[inline(always)]
unsafe fn load_partial_safe(ptr: *const u8, len: usize) -> uint8x16_t {
unsafe {
debug_assert!(len < 16);
if len == 0 {
return vdupq_n_u8(0);
}
if len <= 8 {
let lo = NeonBytes::load_partial_safe(ptr, len);
return vcombine_u8(lo, vdup_n_u8(0));
}
let lo = vld1_u8(ptr);
let hi_len = len - 8;
let hi = NeonBytes::load_partial_safe(ptr.add(8), hi_len);
vcombine_u8(lo, hi)
}
}
}
impl BytesVec for NeonU8Bytes {
type Mask = NeonU8Bytes;
#[inline(always)]
unsafe fn splat(value: u8) -> Self {
unsafe { Self(vdupq_n_u8(value)) }
}
#[inline(always)]
unsafe fn eq(self, other: Self) -> Self::Mask {
unsafe { Self(vceqq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn gt(self, other: Self) -> Self::Mask {
unsafe { Self(vcgtq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn lt(self, other: Self) -> Self::Mask {
unsafe { Self(vcltq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn load_partial(data: *const u8, start: usize, len: usize) -> Self {
unsafe {
let remaining = len.saturating_sub(start);
if remaining == 0 {
return Self(vdupq_n_u8(0));
}
let ptr = data.add(start);
Self(match remaining {
16.. => vld1q_u8(ptr),
1..=15 if can_overread(ptr, 16) => {
let loaded = vld1q_u8(ptr);
let mask_bytes: [u8; 16] = [
if 0 < remaining { 0xFF } else { 0 },
if 1 < remaining { 0xFF } else { 0 },
if 2 < remaining { 0xFF } else { 0 },
if 3 < remaining { 0xFF } else { 0 },
if 4 < remaining { 0xFF } else { 0 },
if 5 < remaining { 0xFF } else { 0 },
if 6 < remaining { 0xFF } else { 0 },
if 7 < remaining { 0xFF } else { 0 },
if 8 < remaining { 0xFF } else { 0 },
if 9 < remaining { 0xFF } else { 0 },
if 10 < remaining { 0xFF } else { 0 },
if 11 < remaining { 0xFF } else { 0 },
if 12 < remaining { 0xFF } else { 0 },
if 13 < remaining { 0xFF } else { 0 },
if 14 < remaining { 0xFF } else { 0 },
if 15 < remaining { 0xFF } else { 0 },
];
vandq_u8(loaded, vld1q_u8(mask_bytes.as_ptr()))
}
_ => Self::load_partial_safe(ptr, remaining),
})
}
}
#[cfg(test)]
fn from_lanes(values: &[u8]) -> Self {
assert_eq!(values.len(), 16);
Self(unsafe { vld1q_u8(values.as_ptr()) })
}
#[cfg(test)]
fn to_lanes(self) -> Vec<u8> {
let mut buf = [0u8; 16];
unsafe { vst1q_u8(buf.as_mut_ptr(), self.0) };
buf.to_vec()
}
}
impl MaskVec for NeonU8Bytes {
#[inline(always)]
unsafe fn zero() -> Self {
unsafe { Self(vdupq_n_u8(0)) }
}
#[inline(always)]
unsafe fn and(self, other: Self) -> Self {
unsafe { Self(vandq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn or(self, other: Self) -> Self {
unsafe { Self(vorrq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn not(self) -> Self {
unsafe { Self(vmvnq_u8(self.0)) }
}
#[inline(always)]
unsafe fn is_zero(self) -> bool {
unsafe { vmaxvq_u8(self.0) == 0 }
}
#[inline(always)]
unsafe fn shift_right_padded_1(self, prev: Self) -> Self {
unsafe { Self(vextq_u8::<15>(prev.0, self.0)) }
}
#[cfg(test)]
fn from_lanes(values: &[bool]) -> Self {
assert_eq!(values.len(), 16);
let mut buf = [0u8; 16];
for i in 0..16 {
buf[i] = if values[i] { 0xFF } else { 0 };
}
Self(unsafe { vld1q_u8(buf.as_ptr()) })
}
#[cfg(test)]
fn to_lanes(self) -> Vec<bool> {
let mut buf = [0u8; 16];
unsafe { vst1q_u8(buf.as_mut_ptr(), self.0) };
buf.iter().map(|&v| v != 0).collect()
}
}
impl ScoreVec for NeonU8Score {
#[inline(always)]
unsafe fn zero() -> Self {
unsafe { Self(vdupq_n_u8(0)) }
}
#[inline(always)]
unsafe fn splat(value: u16) -> Self {
unsafe { Self(vdupq_n_u8(value as u8)) }
}
#[inline(always)]
unsafe fn first_lane(value: u16) -> Self {
unsafe { Self(vsetq_lane_u8::<0>(value as u8, vdupq_n_u8(0))) }
}
#[inline(always)]
unsafe fn max(self, other: Self) -> Self {
unsafe { Self(vmaxq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn horizontal_max(self) -> u16 {
unsafe { vmaxvq_u8(self.0) as u16 }
}
#[inline(always)]
unsafe fn add(self, other: Self) -> Self {
unsafe { Self(vaddq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn subs(self, other: Self) -> Self {
unsafe { Self(vqsubq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn and(self, other: Self) -> Self {
unsafe { Self(vandq_u8(self.0, other.0)) }
}
#[inline(always)]
unsafe fn shift_right_padded<const L: i32>(self, prev: Self) -> Self {
unsafe {
const { assert!(L >= 0 && L <= 16) };
Self(match L {
0 => self.0,
1 => vextq_u8::<15>(prev.0, self.0),
2 => vextq_u8::<14>(prev.0, self.0),
3 => vextq_u8::<13>(prev.0, self.0),
4 => vextq_u8::<12>(prev.0, self.0),
5 => vextq_u8::<11>(prev.0, self.0),
6 => vextq_u8::<10>(prev.0, self.0),
7 => vextq_u8::<9>(prev.0, self.0),
8 => vextq_u8::<8>(prev.0, self.0),
9 => vextq_u8::<7>(prev.0, self.0),
10 => vextq_u8::<6>(prev.0, self.0),
11 => vextq_u8::<5>(prev.0, self.0),
12 => vextq_u8::<4>(prev.0, self.0),
13 => vextq_u8::<3>(prev.0, self.0),
14 => vextq_u8::<2>(prev.0, self.0),
15 => vextq_u8::<1>(prev.0, self.0),
16 => prev.0,
_ => std::hint::unreachable_unchecked(),
})
}
}
#[inline(always)]
unsafe fn find_lane(self, search: u16) -> usize {
unsafe {
let cmp = vceqq_u8(self.0, vdupq_n_u8(search as u8));
let narrowed = vshrn_n_u16::<4>(vreinterpretq_u16_u8(cmp));
let bits = vget_lane_u64::<0>(vreinterpret_u64_u8(narrowed));
if bits == 0 {
16
} else {
bits.trailing_zeros() as usize / 4
}
}
}
#[cfg(test)]
fn from_lanes(values: &[u16]) -> Self {
assert_eq!(values.len(), 16);
let mut buf = [0u8; 16];
for i in 0..16 {
buf[i] = values[i] as u8;
}
Self(unsafe { vld1q_u8(buf.as_ptr()) })
}
#[cfg(test)]
fn to_lanes(self) -> Vec<u16> {
let mut buf = [0u8; 16];
unsafe { vst1q_u8(buf.as_mut_ptr(), self.0) };
buf.iter().map(|&v| v as u16).collect()
}
}