#![allow(clippy::missing_safety_doc)]
pub const MAX_LANES: usize = 8;
pub trait I32x: Copy {
const LANES: usize;
unsafe fn splat(value: i32) -> Self;
unsafe fn load(src: &[i32]) -> Self;
unsafe fn store(self, dst: &mut [i32]);
unsafe fn load_u8(src: &[u8]) -> Self;
unsafe fn store_u8_clamped(self, dst: &mut [u8]);
unsafe fn add(self, other: Self) -> Self;
unsafe fn sub(self, other: Self) -> Self;
unsafe fn mul(self, other: Self) -> Self;
unsafe fn and(self, other: Self) -> Self;
unsafe fn or(self, other: Self) -> Self;
unsafe fn andnot(self, other: Self) -> Self;
unsafe fn min(self, other: Self) -> Self;
unsafe fn max(self, other: Self) -> Self;
unsafe fn abs(self) -> Self;
unsafe fn sra<const N: i32>(self) -> Self;
unsafe fn srl<const N: i32>(self) -> Self;
unsafe fn sll<const N: i32>(self) -> Self;
unsafe fn sra_var(self, bits: i32) -> Self;
unsafe fn gt(self, other: Self) -> Self;
unsafe fn hsum(self) -> i32;
unsafe fn load_i16(src: &[i16]) -> Self;
unsafe fn store_i16(self, dst: &mut [i16]);
unsafe fn div_nonneg(self, divisor: Self) -> Self;
#[inline(always)]
unsafe fn zero() -> Self {
unsafe { Self::splat(0) }
}
#[inline(always)]
unsafe fn select(mask: Self, a: Self, b: Self) -> Self {
unsafe { mask.and(a).or(mask.andnot(b)) }
}
#[inline(always)]
unsafe fn le(self, other: Self) -> Self {
unsafe { self.gt(other).andnot(Self::splat(-1)) }
}
#[inline(always)]
unsafe fn signum(self) -> Self {
unsafe {
let zero = Self::zero();
zero.gt(self).sub(self.gt(zero))
}
}
#[inline(always)]
unsafe fn clamp(self, low: Self, high: Self) -> Self {
unsafe { self.max(low).min(high) }
}
#[inline(always)]
unsafe fn any(self) -> bool {
unsafe { self.hsum() != 0 }
}
#[inline(always)]
unsafe fn store_masked(self, dst: &mut [i32], count: usize) {
unsafe {
if count == Self::LANES {
self.store(dst);
return;
}
let mut scratch = [0i32; MAX_LANES];
self.store(&mut scratch);
dst[..count].copy_from_slice(&scratch[..count]);
}
}
#[inline(always)]
unsafe fn store_u8_clamped_masked(self, dst: &mut [u8], count: usize) {
unsafe {
if count == Self::LANES {
self.store_u8_clamped(dst);
return;
}
let mut scratch = [0i32; MAX_LANES];
self.clamp(Self::zero(), Self::splat(255))
.store(&mut scratch);
for (out, &value) in dst.iter_mut().zip(scratch.iter()).take(count) {
*out = value as u8;
}
}
}
#[inline(always)]
unsafe fn load_u32_rows(src: &[u8], base: usize, stride: usize) -> Self {
let mut words = [0i32; MAX_LANES];
for (lane, slot) in words.iter_mut().enumerate().take(Self::LANES) {
let at = base + lane * stride;
*slot = i32::from_le_bytes([src[at], src[at + 1], src[at + 2], src[at + 3]]);
}
unsafe { Self::load(&words) }
}
#[inline(always)]
unsafe fn store_u32_rows(self, dst: &mut [u8], base: usize, stride: usize) {
let mut words = [0i32; MAX_LANES];
unsafe { self.store(&mut words) };
for (lane, &word) in words.iter().enumerate().take(Self::LANES) {
let at = base + lane * stride;
dst[at..at + 4].copy_from_slice(&word.to_le_bytes());
}
}
}
#[cfg(target_arch = "x86_64")]
pub trait HalfPairs: I32x {
unsafe fn load_halves(low: &[i32], high: &[i32]) -> Self;
unsafe fn splat_halves(low: i32, high: i32) -> Self;
}
pub trait Transpose4: I32x {
unsafe fn transpose4(rows: [Self; 4]) -> [Self; 4];
}
#[cfg(target_arch = "x86_64")]
mod x86 {
use super::{HalfPairs, I32x, Transpose4};
use core::arch::x86_64::*;
#[derive(Clone, Copy)]
pub struct Sse4(__m128i);
impl I32x for Sse4 {
const LANES: usize = 4;
#[inline(always)]
unsafe fn splat(value: i32) -> Self {
unsafe { Self(_mm_set1_epi32(value)) }
}
#[inline(always)]
unsafe fn load(src: &[i32]) -> Self {
unsafe { Self(_mm_loadu_si128(src.as_ptr().cast())) }
}
#[inline(always)]
unsafe fn store(self, dst: &mut [i32]) {
unsafe { _mm_storeu_si128(dst.as_mut_ptr().cast(), self.0) }
}
#[inline(always)]
unsafe fn load_i16(src: &[i16]) -> Self {
unsafe {
Self(_mm_cvtepi16_epi32(_mm_loadl_epi64(
src[..4].as_ptr().cast(),
)))
}
}
#[inline(always)]
unsafe fn store_i16(self, dst: &mut [i16]) {
unsafe {
let low = _mm_and_si128(self.0, _mm_set1_epi32(0xffff));
let packed = _mm_packus_epi32(low, low);
_mm_storel_epi64(dst[..4].as_mut_ptr().cast(), packed);
}
}
#[inline(always)]
unsafe fn div_nonneg(self, divisor: Self) -> Self {
unsafe {
let low = _mm_div_pd(_mm_cvtepi32_pd(self.0), _mm_cvtepi32_pd(divisor.0));
let high = _mm_div_pd(
_mm_cvtepi32_pd(_mm_unpackhi_epi64(self.0, self.0)),
_mm_cvtepi32_pd(_mm_unpackhi_epi64(divisor.0, divisor.0)),
);
Self(_mm_unpacklo_epi64(
_mm_cvttpd_epi32(low),
_mm_cvttpd_epi32(high),
))
}
}
#[inline(always)]
unsafe fn load_u8(src: &[u8]) -> Self {
unsafe {
let bytes = u32::from_le_bytes([src[0], src[1], src[2], src[3]]);
Self(_mm_cvtepu8_epi32(_mm_cvtsi32_si128(bytes as i32)))
}
}
#[inline(always)]
unsafe fn add(self, other: Self) -> Self {
unsafe { Self(_mm_add_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn sub(self, other: Self) -> Self {
unsafe { Self(_mm_sub_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn mul(self, other: Self) -> Self {
unsafe { Self(_mm_mullo_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn and(self, other: Self) -> Self {
unsafe { Self(_mm_and_si128(self.0, other.0)) }
}
#[inline(always)]
unsafe fn or(self, other: Self) -> Self {
unsafe { Self(_mm_or_si128(self.0, other.0)) }
}
#[inline(always)]
unsafe fn andnot(self, other: Self) -> Self {
unsafe { Self(_mm_andnot_si128(self.0, other.0)) }
}
#[inline(always)]
unsafe fn min(self, other: Self) -> Self {
unsafe { Self(_mm_min_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn max(self, other: Self) -> Self {
unsafe { Self(_mm_max_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn abs(self) -> Self {
unsafe { Self(_mm_abs_epi32(self.0)) }
}
#[inline(always)]
unsafe fn sra<const N: i32>(self) -> Self {
unsafe { Self(_mm_srai_epi32::<N>(self.0)) }
}
#[inline(always)]
unsafe fn srl<const N: i32>(self) -> Self {
unsafe { Self(_mm_srli_epi32::<N>(self.0)) }
}
#[inline(always)]
unsafe fn sll<const N: i32>(self) -> Self {
unsafe { Self(_mm_slli_epi32::<N>(self.0)) }
}
#[inline(always)]
unsafe fn load_u32_rows(src: &[u8], base: usize, stride: usize) -> Self {
unsafe {
let word = |lane: usize| {
let at = base + lane * stride;
i32::from_le_bytes([src[at], src[at + 1], src[at + 2], src[at + 3]])
};
Self(_mm_set_epi32(word(3), word(2), word(1), word(0)))
}
}
#[inline(always)]
unsafe fn store_u32_rows(self, dst: &mut [u8], base: usize, stride: usize) {
unsafe {
let words = [
_mm_extract_epi32::<0>(self.0) as u32,
_mm_extract_epi32::<1>(self.0) as u32,
_mm_extract_epi32::<2>(self.0) as u32,
_mm_extract_epi32::<3>(self.0) as u32,
];
for (lane, word) in words.into_iter().enumerate() {
let at = base + lane * stride;
dst[at..at + 4].copy_from_slice(&word.to_le_bytes());
}
}
}
#[inline(always)]
unsafe fn store_u8_clamped(self, dst: &mut [u8]) {
unsafe {
let clamped = _mm_min_epi32(
_mm_max_epi32(self.0, _mm_setzero_si128()),
_mm_set1_epi32(255),
);
let halves = _mm_packus_epi32(clamped, clamped);
let bytes = _mm_packus_epi16(halves, halves);
let word = _mm_cvtsi128_si32(bytes) as u32;
dst[..4].copy_from_slice(&word.to_le_bytes());
}
}
#[inline(always)]
unsafe fn sra_var(self, bits: i32) -> Self {
unsafe { Self(_mm_sra_epi32(self.0, _mm_cvtsi32_si128(bits))) }
}
#[inline(always)]
unsafe fn gt(self, other: Self) -> Self {
unsafe { Self(_mm_cmpgt_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn hsum(self) -> i32 {
unsafe {
let pairs = _mm_hadd_epi32(self.0, self.0);
_mm_cvtsi128_si32(_mm_hadd_epi32(pairs, pairs))
}
}
}
impl Transpose4 for Sse4 {
#[inline(always)]
unsafe fn transpose4(rows: [Self; 4]) -> [Self; 4] {
unsafe {
let a = _mm_unpacklo_epi32(rows[0].0, rows[1].0);
let b = _mm_unpackhi_epi32(rows[0].0, rows[1].0);
let c = _mm_unpacklo_epi32(rows[2].0, rows[3].0);
let d = _mm_unpackhi_epi32(rows[2].0, rows[3].0);
[
Self(_mm_unpacklo_epi64(a, c)),
Self(_mm_unpackhi_epi64(a, c)),
Self(_mm_unpacklo_epi64(b, d)),
Self(_mm_unpackhi_epi64(b, d)),
]
}
}
}
#[derive(Clone, Copy)]
pub struct Avx2(__m256i);
impl I32x for Avx2 {
const LANES: usize = 8;
#[inline(always)]
unsafe fn splat(value: i32) -> Self {
unsafe { Self(_mm256_set1_epi32(value)) }
}
#[inline(always)]
unsafe fn load(src: &[i32]) -> Self {
unsafe { Self(_mm256_loadu_si256(src.as_ptr().cast())) }
}
#[inline(always)]
unsafe fn store(self, dst: &mut [i32]) {
unsafe { _mm256_storeu_si256(dst.as_mut_ptr().cast(), self.0) }
}
#[inline(always)]
unsafe fn load_i16(src: &[i16]) -> Self {
unsafe {
Self(_mm256_cvtepi16_epi32(_mm_loadu_si128(
src[..8].as_ptr().cast(),
)))
}
}
#[inline(always)]
unsafe fn store_i16(self, dst: &mut [i16]) {
unsafe {
let low = _mm256_and_si256(self.0, _mm256_set1_epi32(0xffff));
let packed = _mm_packus_epi32(
_mm256_castsi256_si128(low),
_mm256_extracti128_si256::<1>(low),
);
_mm_storeu_si128(dst[..8].as_mut_ptr().cast(), packed);
}
}
#[inline(always)]
unsafe fn div_nonneg(self, divisor: Self) -> Self {
unsafe {
let low = _mm256_div_pd(
_mm256_cvtepi32_pd(_mm256_castsi256_si128(self.0)),
_mm256_cvtepi32_pd(_mm256_castsi256_si128(divisor.0)),
);
let high = _mm256_div_pd(
_mm256_cvtepi32_pd(_mm256_extracti128_si256::<1>(self.0)),
_mm256_cvtepi32_pd(_mm256_extracti128_si256::<1>(divisor.0)),
);
Self(_mm256_set_m128i(
_mm256_cvttpd_epi32(high),
_mm256_cvttpd_epi32(low),
))
}
}
#[inline(always)]
unsafe fn load_u8(src: &[u8]) -> Self {
unsafe {
let lo = u32::from_le_bytes([src[0], src[1], src[2], src[3]]);
let hi = u32::from_le_bytes([src[4], src[5], src[6], src[7]]);
let packed = _mm_set_epi32(0, 0, hi as i32, lo as i32);
Self(_mm256_cvtepu8_epi32(packed))
}
}
#[inline(always)]
unsafe fn add(self, other: Self) -> Self {
unsafe { Self(_mm256_add_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn sub(self, other: Self) -> Self {
unsafe { Self(_mm256_sub_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn mul(self, other: Self) -> Self {
unsafe { Self(_mm256_mullo_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn and(self, other: Self) -> Self {
unsafe { Self(_mm256_and_si256(self.0, other.0)) }
}
#[inline(always)]
unsafe fn or(self, other: Self) -> Self {
unsafe { Self(_mm256_or_si256(self.0, other.0)) }
}
#[inline(always)]
unsafe fn andnot(self, other: Self) -> Self {
unsafe { Self(_mm256_andnot_si256(self.0, other.0)) }
}
#[inline(always)]
unsafe fn min(self, other: Self) -> Self {
unsafe { Self(_mm256_min_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn max(self, other: Self) -> Self {
unsafe { Self(_mm256_max_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn abs(self) -> Self {
unsafe { Self(_mm256_abs_epi32(self.0)) }
}
#[inline(always)]
unsafe fn sra<const N: i32>(self) -> Self {
unsafe { Self(_mm256_srai_epi32::<N>(self.0)) }
}
#[inline(always)]
unsafe fn srl<const N: i32>(self) -> Self {
unsafe { Self(_mm256_srli_epi32::<N>(self.0)) }
}
#[inline(always)]
unsafe fn sll<const N: i32>(self) -> Self {
unsafe { Self(_mm256_slli_epi32::<N>(self.0)) }
}
#[inline(always)]
unsafe fn load_u32_rows(src: &[u8], base: usize, stride: usize) -> Self {
unsafe {
let word = |lane: usize| {
let at = base + lane * stride;
i32::from_le_bytes([src[at], src[at + 1], src[at + 2], src[at + 3]])
};
let lo = _mm_set_epi32(word(3), word(2), word(1), word(0));
let hi = _mm_set_epi32(word(7), word(6), word(5), word(4));
Self(_mm256_set_m128i(hi, lo))
}
}
#[inline(always)]
unsafe fn store_u32_rows(self, dst: &mut [u8], base: usize, stride: usize) {
unsafe {
let halves = [
_mm256_castsi256_si128(self.0),
_mm256_extracti128_si256::<1>(self.0),
];
for (half, lanes) in halves.into_iter().enumerate() {
let words = [
_mm_extract_epi32::<0>(lanes) as u32,
_mm_extract_epi32::<1>(lanes) as u32,
_mm_extract_epi32::<2>(lanes) as u32,
_mm_extract_epi32::<3>(lanes) as u32,
];
for (lane, word) in words.into_iter().enumerate() {
let at = base + (half * 4 + lane) * stride;
dst[at..at + 4].copy_from_slice(&word.to_le_bytes());
}
}
}
}
#[inline(always)]
unsafe fn store_u8_clamped(self, dst: &mut [u8]) {
unsafe {
let clamped = _mm256_min_epi32(
_mm256_max_epi32(self.0, _mm256_setzero_si256()),
_mm256_set1_epi32(255),
);
let lo = _mm256_castsi256_si128(clamped);
let hi = _mm256_extracti128_si256::<1>(clamped);
let halves = _mm_packus_epi32(lo, hi);
let bytes = _mm_packus_epi16(halves, halves);
let word = _mm_cvtsi128_si64(bytes) as u64;
dst[..8].copy_from_slice(&word.to_le_bytes());
}
}
#[inline(always)]
unsafe fn sra_var(self, bits: i32) -> Self {
unsafe { Self(_mm256_sra_epi32(self.0, _mm_cvtsi32_si128(bits))) }
}
#[inline(always)]
unsafe fn gt(self, other: Self) -> Self {
unsafe { Self(_mm256_cmpgt_epi32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn hsum(self) -> i32 {
unsafe {
let lo = _mm256_castsi256_si128(self.0);
let hi = _mm256_extracti128_si256::<1>(self.0);
let sum = _mm_add_epi32(lo, hi);
let pairs = _mm_hadd_epi32(sum, sum);
_mm_cvtsi128_si32(_mm_hadd_epi32(pairs, pairs))
}
}
}
impl HalfPairs for Avx2 {
#[inline(always)]
unsafe fn load_halves(low: &[i32], high: &[i32]) -> Self {
unsafe {
let lo = _mm_loadu_si128(low.as_ptr().cast());
let hi = _mm_loadu_si128(high.as_ptr().cast());
Self(_mm256_inserti128_si256::<1>(_mm256_castsi128_si256(lo), hi))
}
}
#[inline(always)]
unsafe fn splat_halves(low: i32, high: i32) -> Self {
unsafe {
Self(_mm256_inserti128_si256::<1>(
_mm256_castsi128_si256(_mm_set1_epi32(low)),
_mm_set1_epi32(high),
))
}
}
}
}
#[cfg(target_arch = "x86_64")]
pub use x86::{Avx2, Sse4};
#[cfg(target_arch = "aarch64")]
mod arm {
use super::{I32x, Transpose4};
use core::arch::aarch64::*;
#[derive(Clone, Copy)]
pub struct Neon(int32x4_t);
impl I32x for Neon {
const LANES: usize = 4;
#[inline(always)]
unsafe fn splat(value: i32) -> Self {
unsafe { Self(vdupq_n_s32(value)) }
}
#[inline(always)]
unsafe fn load(src: &[i32]) -> Self {
unsafe { Self(vld1q_s32(src.as_ptr())) }
}
#[inline(always)]
unsafe fn store(self, dst: &mut [i32]) {
unsafe { vst1q_s32(dst.as_mut_ptr(), self.0) }
}
#[inline(always)]
unsafe fn load_i16(src: &[i16]) -> Self {
unsafe { Self(vmovl_s16(vld1_s16(src[..4].as_ptr()))) }
}
#[inline(always)]
unsafe fn store_i16(self, dst: &mut [i16]) {
unsafe { vst1_s16(dst[..4].as_mut_ptr(), vmovn_s32(self.0)) }
}
#[inline(always)]
unsafe fn div_nonneg(self, divisor: Self) -> Self {
unsafe {
let low = vdivq_f64(
vcvtq_f64_s64(vmovl_s32(vget_low_s32(self.0))),
vcvtq_f64_s64(vmovl_s32(vget_low_s32(divisor.0))),
);
let high = vdivq_f64(
vcvtq_f64_s64(vmovl_high_s32(self.0)),
vcvtq_f64_s64(vmovl_high_s32(divisor.0)),
);
Self(vcombine_s32(
vmovn_s64(vcvtq_s64_f64(low)),
vmovn_s64(vcvtq_s64_f64(high)),
))
}
}
#[inline(always)]
unsafe fn load_u8(src: &[u8]) -> Self {
unsafe {
let bytes = u32::from_le_bytes([src[0], src[1], src[2], src[3]]);
let lanes = vreinterpret_u8_u32(vdup_n_u32(bytes));
let widened = vmovl_u16(vget_low_u16(vmovl_u8(lanes)));
Self(vreinterpretq_s32_u32(widened))
}
}
#[inline(always)]
unsafe fn add(self, other: Self) -> Self {
unsafe { Self(vaddq_s32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn sub(self, other: Self) -> Self {
unsafe { Self(vsubq_s32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn mul(self, other: Self) -> Self {
unsafe { Self(vmulq_s32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn and(self, other: Self) -> Self {
unsafe { Self(vandq_s32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn or(self, other: Self) -> Self {
unsafe { Self(vorrq_s32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn andnot(self, other: Self) -> Self {
unsafe { Self(vbicq_s32(other.0, self.0)) }
}
#[inline(always)]
unsafe fn min(self, other: Self) -> Self {
unsafe { Self(vminq_s32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn max(self, other: Self) -> Self {
unsafe { Self(vmaxq_s32(self.0, other.0)) }
}
#[inline(always)]
unsafe fn abs(self) -> Self {
unsafe { Self(vabsq_s32(self.0)) }
}
#[inline(always)]
unsafe fn sra<const N: i32>(self) -> Self {
unsafe {
if N == 0 {
self
} else {
Self(vshlq_s32(self.0, vdupq_n_s32(-N)))
}
}
}
#[inline(always)]
unsafe fn srl<const N: i32>(self) -> Self {
unsafe {
let lanes = vreinterpretq_u32_s32(self.0);
Self(vreinterpretq_s32_u32(vshrq_n_u32::<N>(lanes)))
}
}
#[inline(always)]
unsafe fn sll<const N: i32>(self) -> Self {
unsafe { Self(vshlq_n_s32::<N>(self.0)) }
}
#[inline(always)]
unsafe fn load_u32_rows(src: &[u8], base: usize, stride: usize) -> Self {
unsafe {
let word = |lane: usize| {
let at = base + lane * stride;
u32::from_le_bytes([src[at], src[at + 1], src[at + 2], src[at + 3]])
};
let words = vdupq_n_u32(word(0));
let words = vsetq_lane_u32::<1>(word(1), words);
let words = vsetq_lane_u32::<2>(word(2), words);
let words = vsetq_lane_u32::<3>(word(3), words);
Self(vreinterpretq_s32_u32(words))
}
}
#[inline(always)]
unsafe fn store_u32_rows(self, dst: &mut [u8], base: usize, stride: usize) {
unsafe {
let lanes = vreinterpretq_u32_s32(self.0);
let words = [
vgetq_lane_u32::<0>(lanes),
vgetq_lane_u32::<1>(lanes),
vgetq_lane_u32::<2>(lanes),
vgetq_lane_u32::<3>(lanes),
];
for (lane, word) in words.into_iter().enumerate() {
let at = base + lane * stride;
dst[at..at + 4].copy_from_slice(&word.to_le_bytes());
}
}
}
#[inline(always)]
unsafe fn store_u8_clamped(self, dst: &mut [u8]) {
unsafe {
let halves = vqmovun_s32(self.0);
let bytes = vqmovn_u16(vcombine_u16(halves, halves));
let word = vget_lane_u32::<0>(vreinterpret_u32_u8(bytes));
dst[..4].copy_from_slice(&word.to_le_bytes());
}
}
#[inline(always)]
unsafe fn sra_var(self, bits: i32) -> Self {
unsafe { Self(vshlq_s32(self.0, vdupq_n_s32(-bits))) }
}
#[inline(always)]
unsafe fn gt(self, other: Self) -> Self {
unsafe { Self(vreinterpretq_s32_u32(vcgtq_s32(self.0, other.0))) }
}
#[inline(always)]
unsafe fn hsum(self) -> i32 {
unsafe { vaddvq_s32(self.0) }
}
}
impl Transpose4 for Neon {
#[inline(always)]
unsafe fn transpose4(rows: [Self; 4]) -> [Self; 4] {
unsafe {
let a = vtrn1q_s32(rows[0].0, rows[1].0);
let b = vtrn2q_s32(rows[0].0, rows[1].0);
let c = vtrn1q_s32(rows[2].0, rows[3].0);
let d = vtrn2q_s32(rows[2].0, rows[3].0);
let (a, b) = (vreinterpretq_s64_s32(a), vreinterpretq_s64_s32(b));
let (c, d) = (vreinterpretq_s64_s32(c), vreinterpretq_s64_s32(d));
[
Self(vreinterpretq_s32_s64(vtrn1q_s64(a, c))),
Self(vreinterpretq_s32_s64(vtrn1q_s64(b, d))),
Self(vreinterpretq_s32_s64(vtrn2q_s64(a, c))),
Self(vreinterpretq_s32_s64(vtrn2q_s64(b, d))),
]
}
}
}
}
#[cfg(target_arch = "aarch64")]
pub use arm::Neon;