macro_rules! neon_lane_accessors {
($get:ident, $set:ident; 2) => {
fn extract<const I: usize>(value: Storage<Self>) -> Self::Element {
unsafe {
match I {
0 => arch::$get::<0>(value),
_ => arch::$get::<1>(value),
}
}
}
fn insert<const I: usize>(value: Storage<Self>, element: Self::Element) -> Storage<Self> {
unsafe {
match I {
0 => arch::$set::<0>(element, value),
_ => arch::$set::<1>(element, value),
}
}
}
};
($get:ident, $set:ident; 4) => {
fn extract<const I: usize>(value: Storage<Self>) -> Self::Element {
unsafe {
match I {
0 => arch::$get::<0>(value),
1 => arch::$get::<1>(value),
2 => arch::$get::<2>(value),
_ => arch::$get::<3>(value),
}
}
}
fn insert<const I: usize>(value: Storage<Self>, element: Self::Element) -> Storage<Self> {
unsafe {
match I {
0 => arch::$set::<0>(element, value),
1 => arch::$set::<1>(element, value),
2 => arch::$set::<2>(element, value),
_ => arch::$set::<3>(element, value),
}
}
}
};
($get:ident, $set:ident; 8) => {
fn extract<const I: usize>(value: Storage<Self>) -> Self::Element {
unsafe {
match I {
0 => arch::$get::<0>(value),
1 => arch::$get::<1>(value),
2 => arch::$get::<2>(value),
3 => arch::$get::<3>(value),
4 => arch::$get::<4>(value),
5 => arch::$get::<5>(value),
6 => arch::$get::<6>(value),
_ => arch::$get::<7>(value),
}
}
}
fn insert<const I: usize>(value: Storage<Self>, element: Self::Element) -> Storage<Self> {
unsafe {
match I {
0 => arch::$set::<0>(element, value),
1 => arch::$set::<1>(element, value),
2 => arch::$set::<2>(element, value),
3 => arch::$set::<3>(element, value),
4 => arch::$set::<4>(element, value),
5 => arch::$set::<5>(element, value),
6 => arch::$set::<6>(element, value),
_ => arch::$set::<7>(element, value),
}
}
}
};
($get:ident, $set:ident; 16) => {
fn extract<const I: usize>(value: Storage<Self>) -> Self::Element {
unsafe {
match I {
0 => arch::$get::<0>(value),
1 => arch::$get::<1>(value),
2 => arch::$get::<2>(value),
3 => arch::$get::<3>(value),
4 => arch::$get::<4>(value),
5 => arch::$get::<5>(value),
6 => arch::$get::<6>(value),
7 => arch::$get::<7>(value),
8 => arch::$get::<8>(value),
9 => arch::$get::<9>(value),
10 => arch::$get::<10>(value),
11 => arch::$get::<11>(value),
12 => arch::$get::<12>(value),
13 => arch::$get::<13>(value),
14 => arch::$get::<14>(value),
_ => arch::$get::<15>(value),
}
}
}
fn insert<const I: usize>(value: Storage<Self>, element: Self::Element) -> Storage<Self> {
unsafe {
match I {
0 => arch::$set::<0>(element, value),
1 => arch::$set::<1>(element, value),
2 => arch::$set::<2>(element, value),
3 => arch::$set::<3>(element, value),
4 => arch::$set::<4>(element, value),
5 => arch::$set::<5>(element, value),
6 => arch::$set::<6>(element, value),
7 => arch::$set::<7>(element, value),
8 => arch::$set::<8>(element, value),
9 => arch::$set::<9>(element, value),
10 => arch::$set::<10>(element, value),
11 => arch::$set::<11>(element, value),
12 => arch::$set::<12>(element, value),
13 => arch::$set::<13>(element, value),
14 => arch::$set::<14>(element, value),
_ => arch::$set::<15>(element, value),
}
}
}
};
}
macro_rules! neon_mul_reduce {
($mul:ident, $ext:ident, $get:ident, $v:expr; 2) => {{
let v = $v;
unsafe { arch::$get::<0>(arch::$mul(v, arch::$ext::<1>(v, v))) }
}};
($mul:ident, $ext:ident, $get:ident, $v:expr; 4) => {{
let v = $v;
unsafe {
let t = arch::$mul(v, arch::$ext::<2>(v, v));
arch::$get::<0>(arch::$mul(t, arch::$ext::<1>(t, t)))
}
}};
($mul:ident, $ext:ident, $get:ident, $v:expr; 8) => {{
let v = $v;
unsafe {
let t = arch::$mul(v, arch::$ext::<4>(v, v));
let t = arch::$mul(t, arch::$ext::<2>(t, t));
arch::$get::<0>(arch::$mul(t, arch::$ext::<1>(t, t)))
}
}};
($mul:ident, $ext:ident, $get:ident, $v:expr; 16) => {{
let v = $v;
unsafe {
let t = arch::$mul(v, arch::$ext::<8>(v, v));
let t = arch::$mul(t, arch::$ext::<4>(t, t));
let t = arch::$mul(t, arch::$ext::<2>(t, t));
arch::$get::<0>(arch::$mul(t, arch::$ext::<1>(t, t)))
}
}};
}
macro_rules! neon_mask_core {
(
$reg:ty, lanes: $n:tt($lt:ty), storage: $st:ident, suffix: $s:ident,
truthy: $truthy:expr, from_u: $from_u:ident
) => {
paste::paste! {
#[thermite_macros::inline_always]
impl CoreRegister for $reg {
type NativeIsa = crate::backend::neon::Neon;
type Lanes = $lt;
type Storage = arch::$st;
type Mask = Self;
const IS_EMULATED: bool = false;
const HAS_EQUAL_SIZE_MASK: bool = true;
const EMPTY: Storage<Self> = empty_reg::<Self>();
fn blendv(mask: Storage<Self::Mask>, on_false: Storage<Self>, on_true: Storage<Self>) -> Storage<Self> {
arch::[<neon_bsl_ $s>](mask, on_true, on_false)
}
fn zz(mask: Storage<Self::Mask>, value: Storage<Self>) -> Storage<Self> {
arch::[<neon_and_ $s>](value, mask)
}
fn nz(mask: Storage<Self::Mask>, value: Storage<Self>) -> Storage<Self> {
arch::[<neon_andnot_ $s>](mask, value)
}
fn zeroupper_z<Z: ZeroUpper>(value: Storage<Self>) -> Storage<Self> {
if const { Z::N >= $n } {
value
} else {
arch::[<neon_and_ $s>](value, const { arch::[<neon_keep_mask_ $s>](Z::N) })
}
}
fn from_mask(mask: Storage<Self::Mask>) -> Storage<Self> {
mask
}
}
#[rustfmt::skip] #[thermite_macros::inline_always]
impl BitwiseRegister for $reg {
fn bitxor(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
arch::[<neon_xor_ $s>](lhs, rhs)
}
fn bitand(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
arch::[<neon_and_ $s>](lhs, rhs)
}
fn bitandnot(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
arch::[<neon_andnot_ $s>](lhs, rhs)
}
fn bitor(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
arch::[<neon_or_ $s>](lhs, rhs)
}
fn not(value: Storage<Self>) -> Storage<Self> {
arch::[<neon_not_ $s>](value)
}
}
#[thermite_macros::inline_always]
impl InterleaveRegister for $reg {
fn interleave(a: Storage<Self>, b: Storage<Self>) -> (Storage<Self>, Storage<Self>) {
unsafe { (arch::[<vzip1q_ $s>](a, b), arch::[<vzip2q_ $s>](a, b)) }
}
fn deinterleave(a: Storage<Self>, b: Storage<Self>) -> (Storage<Self>, Storage<Self>) {
unsafe { (arch::[<vuzp1q_ $s>](a, b), arch::[<vuzp2q_ $s>](a, b)) }
}
}
#[thermite_macros::inline_always]
impl MaskRegister for $reg {
const FALSY: Storage<Self> = empty_reg::<Self>();
const TRUTHY: Storage<Self> = reg::<Self, $n>([$truthy; $n]);
fn set(mut mask: Storage<Self>, lane: usize, value: bool) -> Storage<Self> {
Self::as_mut_slice(&mut mask)[lane] = if value { MaskElement::TRUTHY } else { MaskElement::FALSY };
mask
}
fn test(mask: Storage<Self>, lane: usize) -> bool {
Self::as_slice(&mask)[lane].to_bool()
}
fn new_mask(value: GenericArray<bool, Self::Lanes>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<neon_bools_to_mask_x $n>](value)) }
}
fn all(value: Storage<Self>) -> bool {
arch::[<neon_mask_all_ $s>](value)
}
fn any(value: Storage<Self>) -> bool {
arch::[<neon_mask_any_ $s>](value)
}
fn native_bitmask(value: Storage<Self>) -> Option<u64> {
Some(arch::[<neon_movemask_ $s>](value))
}
fn from_native_bitmask(bitmask: u64) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<neon_frombitmask_x $n>](bitmask)) }
}
fn count_set<const N: usize>(values: [Storage<Self>; N]) -> usize {
arch::[<neon_count_mask_ $s>](values)
}
#[cfg(feature = "bitvec")]
fn fill_bitmask(value: Storage<Self>, view: &mut bitvec::slice::BitSlice<u32>) {
let mask = arch::[<neon_movemask_ $s>](value) as u32;
let mask = bitvec::slice::BitSlice::from_slice(core::slice::from_ref(&mask));
view.copy_from_bitslice(&mask[..<Self::Lanes as Unsigned>::USIZE]);
}
}
#[thermite_macros::inline_always]
impl CastMaskRegister<$reg> for $reg {
fn mask_from(value: Storage<Self>) -> Storage<Self> {
value
}
}
}
};
}
macro_rules! neon_register {
(
$reg:ty, elem: $e:ty, lanes: $n:tt, suffix: $s:ident, vec: $vt:ident,
signed: $sg:ty, unsigned: $un:ty,
compress: $compress:tt, bytes: ($to_b:ident, $from_b:ident)
$(, extras: { $($extras:tt)* })?
) => {
paste::paste! {
#[thermite_macros::inline_always]
impl Register for $reg {
type Element = $e;
type Signed = $sg;
type Unsigned = $un;
const HAS_STRUCTURAL_MEMOPS: bool = true;
fn into_mask(value: Storage<Self>) -> Storage<Self::Mask> {
arch::[<neon_nonzero_mask_ $s>](value)
}
fn into_mask_unchecked(value: Storage<Self>) -> Storage<Self::Mask> {
value
}
fn msb_to_mask(value: Storage<Self>) -> Storage<Self::Mask> {
arch::[<neon_msb_mask_ $s>](value)
}
fn new(value: GenericArray<Self::Element, Self::Lanes>) -> Storage<Self> {
unsafe { arch::[<vld1q_ $s>](value.as_slice().as_ptr()) }
}
fn single(value: Self::Element) -> Storage<Self> {
unsafe { arch::[<vsetq_lane_ $s>]::<0>(value, Self::EMPTY) }
}
fn splat(value: Self::Element) -> Storage<Self> {
unsafe { arch::[<vdupq_n_ $s>](value) }
}
unsafe fn load(ptr: *const Self::Element) -> Storage<Self> {
unsafe { arch::[<vld1q_ $s>](ptr) }
}
unsafe fn store(ptr: *mut Self::Element, value: Storage<Self>) {
unsafe { arch::[<vst1q_ $s>](ptr, value) }
}
fn reverse(value: Storage<Self>) -> Storage<Self> {
arch::[<neon_reverse_ $s>](value)
}
fn swap_bytes(value: Storage<Self>) -> Storage<Self> {
arch::[<neon_swap_bytes_ $s>](value)
}
neon_lane_accessors!([<vgetq_lane_ $s>], [<vsetq_lane_ $s>]; $n);
neon_broadcast_align!([<vdupq_laneq_ $s>], [<vextq_ $s>]; $n);
const HAS_PERMUTEV: bool = true;
fn permutev(value: Storage<Self>, idxs: GenericArray<u32, Self::Lanes>) -> Storage<Self> {
let idxs: [u32; $n] = unsafe { core::mem::transmute(idxs) };
arch::[<neon_tbl_ $s>](value, unsafe { arch::neon_lane_table_dyn::<$n, $n>(idxs) })
}
fn swizzle(a: Storage<Self>, b: Storage<Self>, idxs: GenericArray<u32, Self::Lanes>) -> Storage<Self> {
let idxs: [u32; $n] = unsafe { core::mem::transmute(idxs) };
arch::[<neon_tbl2_ $s>](a, b, unsafe { arch::neon_lane_table_dyn::<$n, { 2 * $n }>(idxs) })
}
fn swizzle_const<I: crate::swizzle::SwizzleIndices<Self::Lanes>>(
a: Storage<Self>,
b: Storage<Self>,
) -> Storage<Self> {
arch::[<neon_tbl2_ $s>](a, b, const {
arch::neon_lane_table::<$n>(16 / $n, unsafe {
crate::generic_array::const_transmute(I::INDICES)
})
})
}
fn array_permutev<const M: usize>(value: [Storage<Self>; M], idxs: &[u32]) -> [Storage<Self>; M] {
const ES: usize = 16 / $n; let l = $n;
let total = M * l;
if const { M >= 1 && M <= 4 } {
let idxs = &idxs[..total];
unsafe {
let v = value.as_slice();
let mut table = [arch::vdupq_n_u8(0); 4];
let mut j = 0;
while j < M {
table[j] = arch::$to_b(v[j]);
j += 1;
}
let mut out = [Self::EMPTY; M];
let mut i = 0;
while i < M {
let mut bytes = [0u8; 16];
let mut lane = 0;
while lane < l {
let g = idxs[i * l + lane] as usize;
let g = if const { (M * $n).is_power_of_two() } {
g & (total - 1)
} else {
g.min(total - 1)
};
let mut b = 0;
while b < ES {
bytes[lane * ES + b] = (g * ES + b) as u8;
b += 1;
}
lane += 1;
}
let idxv = arch::vld1q_u8(bytes.as_ptr());
out[i] = arch::$from_b(arch::neon_tbl_n_u8::<M>(table, idxv));
i += 1;
}
return out;
}
}
let mut out = [Self::EMPTY; M];
for i in 0..M {
let mut arr: GenericArray<Self::Element, Self::Lanes> = GenericArray::default();
for lane in 0..l {
let g = idxs[i * l + lane] as usize;
let g = if total.is_power_of_two() { g & (total - 1) } else { g.min(total - 1) };
arr[lane] = Self::as_slice(&value[g / l])[g % l];
}
out[i] = Self::new(arr);
}
out
}
fn array_swizzle<const M: usize>(
a: [Storage<Self>; M],
b: [Storage<Self>; M],
idxs: &[u32],
) -> [Storage<Self>; M] {
const ES: usize = 16 / $n;
let l = $n;
let total = M * l;
let span = 2 * total;
if const { M >= 1 && M <= 2 } {
let idxs = &idxs[..total];
unsafe {
let (av, bv) = (a.as_slice(), b.as_slice());
let mut table = [arch::vdupq_n_u8(0); 4];
let mut j = 0;
while j < M {
table[j] = arch::$to_b(av[j]);
table[M + j] = arch::$to_b(bv[j]);
j += 1;
}
let mut out = [Self::EMPTY; M];
let mut i = 0;
while i < M {
let mut bytes = [0u8; 16];
let mut lane = 0;
while lane < l {
let g = idxs[i * l + lane] as usize;
let g = if const { (2 * M * $n).is_power_of_two() } {
g & (span - 1)
} else {
g.min(span - 1)
};
let mut bb = 0;
while bb < ES {
bytes[lane * ES + bb] = (g * ES + bb) as u8;
bb += 1;
}
lane += 1;
}
let idxv = arch::vld1q_u8(bytes.as_ptr());
out[i] = arch::$from_b(if const { M == 1 } {
arch::neon_tbl_n_u8::<2>(table, idxv)
} else {
arch::neon_tbl_n_u8::<4>(table, idxv)
});
i += 1;
}
return out;
}
}
let mut out = [Self::EMPTY; M];
for i in 0..M {
let mut arr: GenericArray<Self::Element, Self::Lanes> = GenericArray::default();
for lane in 0..l {
let g = idxs[i * l + lane] as usize;
let g = if span.is_power_of_two() { g & (span - 1) } else { g.min(span - 1) };
let src = if g / l < M { &a[g / l] } else { &b[g / l - M] };
arr[lane] = Self::as_slice(src)[g % l];
}
out[i] = Self::new(arr);
}
out
}
fn deinterleave_radix<const RADIX: usize>(
inputs: [Storage<Self>; RADIX],
) -> [Storage<Self>; RADIX] {
if const { RADIX == 3 } {
const ES: usize = 16 / $n;
const fn table<const R: usize>() -> arch::uint8x16_t {
let mut bytes = [0u8; 16];
let mut l = 0;
while l < $n {
let g = l * 3 + R;
let mut b = 0;
while b < ES {
bytes[l * ES + b] = (g * ES + b) as u8;
b += 1;
}
l += 1;
}
arch::cu8x16(bytes)
}
let (a, b, c) = unsafe {
(*inputs.get_unchecked(0), *inputs.get_unchecked(1), *inputs.get_unchecked(2))
};
let (o0, o1, o2) = unsafe {
let t = [arch::$to_b(a), arch::$to_b(b), arch::$to_b(c), arch::vdupq_n_u8(0)];
(
arch::$from_b(arch::neon_tbl_n_u8::<3>(t, const { table::<0>() })),
arch::$from_b(arch::neon_tbl_n_u8::<3>(t, const { table::<1>() })),
arch::$from_b(arch::neon_tbl_n_u8::<3>(t, const { table::<2>() })),
)
};
let mut out = [Self::EMPTY; RADIX];
unsafe {
*out.get_unchecked_mut(0) = o0;
*out.get_unchecked_mut(1) = o1;
*out.get_unchecked_mut(2) = o2;
}
out
} else {
crate::backend::generic::polyfills::deinterleave_radix_default::<Self, RADIX>(inputs)
}
}
fn interleave_radix<const RADIX: usize>(
inputs: [Storage<Self>; RADIX],
) -> [Storage<Self>; RADIX] {
if const { RADIX == 3 } {
const ES: usize = 16 / $n;
const fn table<const I: usize>() -> arch::uint8x16_t {
let mut bytes = [0u8; 16];
let mut l = 0;
while l < $n {
let g = I * $n + l;
let src = (g % 3) * $n + (g / 3);
let mut b = 0;
while b < ES {
bytes[l * ES + b] = (src * ES + b) as u8;
b += 1;
}
l += 1;
}
arch::cu8x16(bytes)
}
let (x, y, z) = unsafe {
(*inputs.get_unchecked(0), *inputs.get_unchecked(1), *inputs.get_unchecked(2))
};
let (o0, o1, o2) = unsafe {
let t = [arch::$to_b(x), arch::$to_b(y), arch::$to_b(z), arch::vdupq_n_u8(0)];
(
arch::$from_b(arch::neon_tbl_n_u8::<3>(t, const { table::<0>() })),
arch::$from_b(arch::neon_tbl_n_u8::<3>(t, const { table::<1>() })),
arch::$from_b(arch::neon_tbl_n_u8::<3>(t, const { table::<2>() })),
)
};
let mut out = [Self::EMPTY; RADIX];
unsafe {
*out.get_unchecked_mut(0) = o0;
*out.get_unchecked_mut(1) = o1;
*out.get_unchecked_mut(2) = o2;
}
out
} else {
crate::backend::generic::polyfills::interleave_radix_default::<Self, RADIX>(inputs)
}
}
unsafe fn load_deinterleaved<const N: usize>(ptr: *const Self::Element) -> [Storage<Self>; N] {
let mut out = [Self::EMPTY; N];
{
let o = out.as_mut_slice();
unsafe {
match N {
2 => {
let v = arch::[<vld2q_ $s>](ptr);
o[0] = v.0;
o[1] = v.1;
}
3 => {
let v = arch::[<vld3q_ $s>](ptr);
o[0] = v.0;
o[1] = v.1;
o[2] = v.2;
}
4 => {
let v = arch::[<vld4q_ $s>](ptr);
o[0] = v.0;
o[1] = v.1;
o[2] = v.2;
o[3] = v.3;
}
_ => {
let mut src = [Self::EMPTY; N];
for (i, s) in src.iter_mut().enumerate() {
*s = Self::load_unaligned(ptr.add(i * $n));
}
return crate::backend::generic::polyfills::deinterleave_n::<Self, N>(src);
}
}
}
}
out
}
unsafe fn store_interleaved<const N: usize>(ptr: *mut Self::Element, values: [Storage<Self>; N]) {
let v = values.as_slice();
unsafe {
match N {
2 => arch::[<vst2q_ $s>](ptr, arch::[<$vt x2_t>](v[0], v[1])),
3 => arch::[<vst3q_ $s>](ptr, arch::[<$vt x3_t>](v[0], v[1], v[2])),
4 => arch::[<vst4q_ $s>](ptr, arch::[<$vt x4_t>](v[0], v[1], v[2], v[3])),
_ => {
let out = crate::backend::generic::polyfills::interleave_n::<Self, N>(values);
for (i, s) in out.iter().enumerate() {
Self::store_unaligned(ptr.add(i * $n), *s);
}
}
}
}
}
neon_compress_sel!($compress);
$($($extras)*)?
}
}
};
}
macro_rules! neon_compress_sel {
(table) => {
compress_via_table!();
};
(wide) => {
compress_via_wide!();
};
}
macro_rules! neon_partial_ord {
($reg:ty, suffix: $s:ident, from_u: $from_u:ident) => {
paste::paste! {
#[rustfmt::skip] #[thermite_macros::inline_always]
impl PartialOrdRegister for $reg {
fn eq(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<vceqq_ $s>](lhs, rhs)) }
}
fn gt(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<vcgtq_ $s>](lhs, rhs)) }
}
fn ge(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<vcgeq_ $s>](lhs, rhs)) }
}
fn lt(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<vcltq_ $s>](lhs, rhs)) }
}
fn le(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<vcleq_ $s>](lhs, rhs)) }
}
}
}
};
}
macro_rules! neon_shuffle_permute {
($reg:ty, suffix: $s:ident, from_u: $from_u:ident; 4) => {
paste::paste! {
#[thermite_macros::inline_always]
impl ShuffleRegister for $reg {
fn shuffle<const IMM8: i32>(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
Self::blendv(
unsafe { arch::$from_u(const { arch::neon_imm8x4_to_mask::<IMM8>() }) },
lhs,
rhs,
)
}
}
#[thermite_macros::inline_always]
impl PermuteRegister for $reg {
fn permute<const IMM8: i32>(value: Storage<Self>) -> Storage<Self> {
arch::[<neon_tbl_ $s>](value, const { arch::neon_imm8x4_to_table::<IMM8>() })
}
}
}
};
($reg:ty, suffix: $s:ident, from_u: $from_u:ident; 2) => {
paste::paste! {
#[thermite_macros::inline_always]
impl ShuffleRegister for $reg {
fn shuffle<const IMM8: i32>(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
Self::blendv(
unsafe { arch::$from_u(const { arch::neon_imm8x2_to_mask::<IMM8>() }) },
lhs,
rhs,
)
}
}
#[thermite_macros::inline_always]
impl PermuteRegister for $reg {
fn permute<const IMM8: i32>(value: Storage<Self>) -> Storage<Self> {
arch::[<neon_tbl_ $s>](value, const { arch::neon_imm8x2_to_table::<IMM8>() })
}
}
}
};
}
macro_rules! neon_int_numeric {
($reg:ty, elem: $e:ty, lanes: $n:tt, suffix: $s:ident) => {
paste::paste! {
#[thermite_macros::inline_always]
impl NumericRegister for $reg {
sort_via_network!($n);
const ZERO: Storage<Self> = reg::<Self, $n>([0 as $e; $n]);
const ONE: Storage<Self> = reg::<Self, $n>([1 as $e; $n]);
const TWO: Storage<Self> = reg::<Self, $n>([2 as $e; $n]);
const MIN: Storage<Self> = reg::<Self, $n>([<$e>::MIN; $n]);
const MAX: Storage<Self> = reg::<Self, $n>([<$e>::MAX; $n]);
fn is_all_zero(value: Storage<Self>) -> bool {
!arch::[<neon_mask_any_ $s>](value)
}
fn min_element(value: Storage<Self>) -> Self::Element {
unsafe { arch::[<vminvq_ $s>](value) }
}
fn max_element(value: Storage<Self>) -> Self::Element {
unsafe { arch::[<vmaxvq_ $s>](value) }
}
fn sum_elements(value: Storage<Self>) -> Self::Element {
unsafe { arch::[<vaddvq_ $s>](value) }
}
fn prod_elements(value: Storage<Self>) -> Self::Element {
neon_mul_reduce!([<vmulq_ $s>], [<vextq_ $s>], [<vgetq_lane_ $s>], value; $n)
}
fn pairwise_sum(lo: Storage<Self>, hi: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vpaddq_ $s>](lo, hi) }
}
fn offset() -> Storage<Self> {
Self::splat(<Self::Lanes as Unsigned>::USIZE as $e)
}
fn indexed() -> Storage<Self> {
const INDEXED: Storage<$reg> = {
let mut a = [0 as $e; $n];
let mut i = 0;
while i < $n {
a[i] = i as $e;
i += 1;
}
reg::<$reg, $n>(a)
};
INDEXED
}
fn add(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vaddq_ $s>](lhs, rhs) }
}
fn sub(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vsubq_ $s>](lhs, rhs) }
}
fn mul(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vmulq_ $s>](lhs, rhs) }
}
fn div(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
Self::zip(lhs, rhs, |a, b| if b == 0 { 0 } else { a.wrapping_div(b) })
}
fn rem(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
Self::zip(lhs, rhs, |a, b| if b == 0 { 0 } else { a.wrapping_rem(b) })
}
fn min(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vminq_ $s>](lhs, rhs) }
}
fn max(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vmaxq_ $s>](lhs, rhs) }
}
}
}
};
}
macro_rules! neon_bitshift {
(
$reg:ty, suffix: $s:ident, unsigned: $us:ident, count: ($ce:ty, $cs:ident),
to_u: $to_u:ident, from_u: $from_u:ident, to_c: $to_c:ident,
bytes: ($to_b:ident, $from_b:ident)
) => {
paste::paste! {
#[thermite_macros::inline_always]
impl BitshiftRegister for $reg {
const HAS_TRUE_SHIFTV: bool = true;
const HAS_WIDE_BYTE_SHIFTS: bool = true;
fn shl(value: Storage<Self>, shift: u32) -> Storage<Self> {
unsafe { arch::[<vshlq_ $s>](value, arch::[<vdupq_n_ $cs>](shift as $ce)) }
}
fn shr(value: Storage<Self>, shift: u32) -> Storage<Self> {
unsafe {
arch::$from_u(arch::[<vshlq_ $us>](
arch::$to_u(value),
arch::[<vdupq_n_ $cs>](-(shift as $ce)),
))
}
}
fn shlv(value: Storage<Self>, shifts: Storage<Self::Unsigned>) -> Storage<Self> {
unsafe { arch::[<vshlq_ $s>](value, arch::$to_c(shifts)) }
}
fn shrv(value: Storage<Self>, shifts: Storage<Self::Unsigned>) -> Storage<Self> {
unsafe {
arch::$from_u(arch::[<vshlq_ $us>](
arch::$to_u(value),
arch::[<vnegq_ $cs>](arch::$to_c(shifts)),
))
}
}
fn bshli<const IMM8: i32>(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_b(arch::neon_bshli_u8x16::<IMM8>(arch::$to_b(value))) }
}
fn bshri<const IMM8: i32>(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_b(arch::neon_bshri_u8x16::<IMM8>(arch::$to_b(value))) }
}
fn roli<const IMM8: i32>(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<neon_roli_ $us>]::<IMM8>(arch::$to_u(value))) }
}
fn rori<const IMM8: i32>(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<neon_rori_ $us>]::<IMM8>(arch::$to_u(value))) }
}
fn reverse_bits(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<neon_bitrev_ $us>](arch::$to_u(value))) }
}
}
}
};
}
macro_rules! neon_int_register {
($reg:ty, suffix: $s:ident, unsigned: $us:ident, to_u: $to_u:ident, from_u: $from_u:ident, div: $div:ident) => {
paste::paste! {
#[thermite_macros::inline_always]
impl IntegerRegister for $reg {
fn mulhi(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
arch::[<neon_mulhi_ $s>](lhs, rhs)
}
fn mullo(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vmulq_ $s>](lhs, rhs) }
}
fn saturating_add(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vqaddq_ $s>](lhs, rhs) }
}
fn saturating_sub(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vqsubq_ $s>](lhs, rhs) }
}
fn div_branched(value: Storage<Self>, divider: crate::Divider<Self::Element>) -> Storage<Self> {
arch::[<div_ $div>]::<Self>(value, divider.multiplier(), divider.shift())
}
fn div_branchfree(value: Storage<Self>, divider: crate::BranchfreeDivider<Self::Element>) -> Storage<Self> {
arch::[<div_ $div _bf>]::<Self>(value, divider.multiplier(), divider.shift())
}
fn divv_branchfree(value: Storage<Self>, dividers: crate::divider::vector::VectorDivider<Self>) -> Storage<Self> {
arch::[<divv_ $div _bf>]::<Self>(value, dividers.multipliers.0, dividers.shifts.0)
}
const HAS_HARDWARE_POPCNT: bool = true;
fn count_ones(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<neon_popcnt_ $us>](arch::$to_u(value))) }
}
fn leading_zeros(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<neon_clz_ $us>](arch::$to_u(value))) }
}
fn trailing_zeros(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::[<neon_ctz_ $us>](arch::$to_u(value))) }
}
}
}
};
}
macro_rules! neon_float_register {
(
$reg:ty, elem: $e:ty, lanes: $n:tt, suffix: $s:ident, from_u: $from_u:ident,
bits: $bits:ty, signed_bits: $sbits:ty, extended: $ext:ty,
exp_mask: $exp_mask:expr, approx: $approx:tt
) => {
paste::paste! {
#[thermite_macros::inline_always]
impl NumericRegister for $reg {
sort_via_network!($n);
const ZERO: Storage<Self> = reg::<Self, $n>([0.0; $n]);
const ONE: Storage<Self> = reg::<Self, $n>([1.0; $n]);
const TWO: Storage<Self> = reg::<Self, $n>([2.0; $n]);
const MIN: Storage<Self> = reg::<Self, $n>([<$e>::MIN; $n]);
const MAX: Storage<Self> = reg::<Self, $n>([<$e>::MAX; $n]);
fn min_element(value: Storage<Self>) -> Self::Element {
cfg_select! {
feature = "strict_ieee754" => {
unsafe { arch::[<vminnmvq_ $s>](value) }
}
_ => unsafe { arch::[<vminvq_ $s>](value) },
}
}
fn max_element(value: Storage<Self>) -> Self::Element {
cfg_select! {
feature = "strict_ieee754" => {
unsafe { arch::[<vmaxnmvq_ $s>](value) }
}
_ => unsafe { arch::[<vmaxvq_ $s>](value) },
}
}
fn sum_elements(value: Storage<Self>) -> Self::Element {
unsafe { arch::[<vaddvq_ $s>](value) }
}
fn prod_elements(value: Storage<Self>) -> Self::Element {
neon_mul_reduce!([<vmulq_ $s>], [<vextq_ $s>], [<vgetq_lane_ $s>], value; $n)
}
fn pairwise_sum(lo: Storage<Self>, hi: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vpaddq_ $s>](lo, hi) }
}
fn offset() -> Storage<Self> {
Self::splat(<Self::Lanes as Unsigned>::USIZE as $e)
}
fn indexed() -> Storage<Self> {
const INDEXED: Storage<$reg> = {
let mut a = [0.0 as $e; $n];
let mut i = 0;
while i < $n {
a[i] = i as $e;
i += 1;
}
reg::<$reg, $n>(a)
};
INDEXED
}
fn add(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vaddq_ $s>](lhs, rhs) }
}
fn sub(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vsubq_ $s>](lhs, rhs) }
}
fn mul(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vmulq_ $s>](lhs, rhs) }
}
fn div(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vdivq_ $s>](lhs, rhs) }
}
fn rem(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
Self::nmul_adde(Self::trunc(Self::div(lhs, rhs)), rhs, lhs)
}
fn min(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
cfg_select! {
feature = "strict_ieee754" => {
unsafe { arch::[<vminnmq_ $s>](lhs, rhs) }
}
_ => unsafe { arch::[<vminq_ $s>](lhs, rhs) },
}
}
fn max(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
cfg_select! {
feature = "strict_ieee754" => {
unsafe { arch::[<vmaxnmq_ $s>](lhs, rhs) }
}
_ => unsafe { arch::[<vmaxq_ $s>](lhs, rhs) },
}
}
}
#[thermite_macros::inline_always]
impl SignedRegister for $reg {
const NEG_ONE: Storage<Self> = reg::<Self, $n>([-1.0; $n]);
const MIN_POSITIVE: Storage<Self> = reg::<Self, $n>([<$e>::MIN_POSITIVE; $n]);
fn neg(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vnegq_ $s>](value) }
}
fn abs(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vabsq_ $s>](value) }
}
fn copysign(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
Self::bitor(Self::bitandnot(Self::NEG_ZERO, lhs), Self::bitand(Self::NEG_ZERO, rhs))
}
fn signum(value: Storage<Self>) -> Storage<Self> {
Self::bitor(Self::ONE, Self::bitand(value, Self::NEG_ZERO))
}
fn neg_c(mask: Storage<Self::Mask>, value: Storage<Self>) -> Storage<Self> {
Self::bitxor(value, Self::bitand(Self::NEG_ZERO, mask))
}
}
#[thermite_macros::inline_always]
impl FloatRegister for $reg {
const HAS_TRUE_FMA: bool = true;
type Bits = $bits;
type SignedBits = $sbits;
type ExtendedPrecision = $ext;
const HALF: Storage<Self> = reg::<Self, $n>([0.5; $n]);
const NEG_ZERO: Storage<Self> = reg::<Self, $n>([-0.0; $n]);
const EPSILON: Storage<Self> = reg::<Self, $n>([<$e>::EPSILON; $n]);
const INFINITY: Storage<Self> = reg::<Self, $n>([<$e>::INFINITY; $n]);
const NEG_INFINITY: Storage<Self> = reg::<Self, $n>([<$e>::NEG_INFINITY; $n]);
const NAN: Storage<Self> = reg::<Self, $n>([<$e>::NAN; $n]);
const EXP_MASK: Storage<Self::Bits> = reg::<$bits, $n>([$exp_mask; $n]);
fn mul_add(lhs: Storage<Self>, rhs: Storage<Self>, acc: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vfmaq_ $s>](acc, lhs, rhs) }
}
fn mul_sub(lhs: Storage<Self>, rhs: Storage<Self>, acc: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vnegq_ $s>](arch::[<vfmsq_ $s>](acc, lhs, rhs)) }
}
fn nmul_add(lhs: Storage<Self>, rhs: Storage<Self>, acc: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vfmsq_ $s>](acc, lhs, rhs) }
}
fn nmul_sub(lhs: Storage<Self>, rhs: Storage<Self>, acc: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vnegq_ $s>](arch::[<vfmaq_ $s>](acc, lhs, rhs)) }
}
fn mul_adde(lhs: Storage<Self>, rhs: Storage<Self>, acc: Storage<Self>) -> Storage<Self> {
Self::mul_add(lhs, rhs, acc)
}
fn mul_sube(lhs: Storage<Self>, rhs: Storage<Self>, acc: Storage<Self>) -> Storage<Self> {
Self::mul_sub(lhs, rhs, acc)
}
fn nmul_adde(lhs: Storage<Self>, rhs: Storage<Self>, acc: Storage<Self>) -> Storage<Self> {
Self::nmul_add(lhs, rhs, acc)
}
fn nmul_sube(lhs: Storage<Self>, rhs: Storage<Self>, acc: Storage<Self>) -> Storage<Self> {
Self::nmul_sub(lhs, rhs, acc)
}
fn sqrt(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vsqrtq_ $s>](value) }
}
fn is_infinite(value: Storage<Self>) -> Storage<Self::Mask> {
unsafe { arch::$from_u(arch::[<vcageq_ $s>](value, Self::INFINITY)) }
}
fn is_finite(value: Storage<Self>) -> Storage<Self::Mask> {
unsafe { arch::$from_u(arch::[<vcaltq_ $s>](value, Self::INFINITY)) }
}
neon_approx_recip!($approx, $s);
fn floor(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vrndmq_ $s>](value) }
}
fn ceil(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vrndpq_ $s>](value) }
}
fn round(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vrndnq_ $s>](value) }
}
fn trunc(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vrndq_ $s>](value) }
}
const NATIVE_CAP: NativeCapability = NativeCapability::NONE;
}
}
};
}
macro_rules! neon_signed_int {
(
$reg:ty, lanes: $n:tt, suffix: $s:ident, count: $ce:ty, from_u: $from_u:ident, to_c: $to_c:ident
$(, signed_extras: { $($sx:tt)* })?
$(, extras: { $($extras:tt)* })?
) => {
paste::paste! {
#[thermite_macros::inline_always]
impl SignedRegister for $reg {
const NEG_ONE: Storage<Self> = reg::<Self, $n>([-1; $n]);
const MIN_POSITIVE: Storage<Self> = reg::<Self, $n>([1; $n]);
fn neg(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vnegq_ $s>](value) }
}
fn abs(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vabsq_ $s>](value) }
}
fn is_negative(value: Storage<Self>) -> Storage<Self::Mask> {
unsafe { arch::$from_u(arch::[<vcltzq_ $s>](value)) }
}
fn is_positive(value: Storage<Self>) -> Storage<Self::Mask> {
unsafe { arch::$from_u(arch::[<vcgezq_ $s>](value)) }
}
$($($sx)*)?
}
#[thermite_macros::inline_always]
impl SignedIntegerRegister for $reg {
fn sra(value: Storage<Self>, shift: u32) -> Storage<Self> {
unsafe { arch::[<vshlq_ $s>](value, arch::[<vdupq_n_ $s>](-(shift as $ce))) }
}
fn srav(value: Storage<Self>, shifts: Storage<Self::Unsigned>) -> Storage<Self> {
unsafe { arch::[<vshlq_ $s>](value, arch::[<vnegq_ $s>](arch::$to_c(shifts))) }
}
$($($extras)*)?
}
}
};
}
macro_rules! neon_unsigned_int {
(
$reg:ty, suffix: $s:ident, neg: ($ss:ident, $to_s:ident)
$(, extras: { $($extras:tt)* })?
) => {
paste::paste! {
#[thermite_macros::inline_always]
impl UnsignedIntegerRegister for $reg {
fn avg(a: Storage<Self>, b: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vrhaddq_ $s>](a, b) }
}
fn abs_diff(a: Storage<Self>, b: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vabdq_ $s>](a, b) }
}
fn next_power_of_two_m1(value: Storage<Self>) -> Storage<Self> {
unsafe {
let clz = arch::[<neon_clz_ $s>](value);
arch::[<vshlq_ $s>](Self::MAX, arch::[<vnegq_ $ss>](arch::$to_s(clz)))
}
}
fn ilog2p1(value: Storage<Self>) -> Storage<Self> {
unsafe {
let w = Self::splat((core::mem::size_of::<<Self as Register>::Element>() * 8) as _);
arch::[<vsubq_ $s>](w, arch::[<neon_clz_ $s>](value))
}
}
$($($extras)*)?
}
}
};
}
macro_rules! neon_int64_register {
(
$reg:ty, elem: $e:ty, suffix: $s:ident, wide: $w:ty,
minmax: ($min:ident, $max:ident), to_u: $to_u:ident, from_u: $from_u:ident, div: $div:ident
) => {
paste::paste! {
#[thermite_macros::inline_always]
impl NumericRegister for $reg {
sort_via_network!(2);
const ZERO: Storage<Self> = reg::<Self, 2>([0; 2]);
const ONE: Storage<Self> = reg::<Self, 2>([1; 2]);
const TWO: Storage<Self> = reg::<Self, 2>([2; 2]);
const MIN: Storage<Self> = reg::<Self, 2>([<$e>::MIN; 2]);
const MAX: Storage<Self> = reg::<Self, 2>([<$e>::MAX; 2]);
fn is_all_zero(value: Storage<Self>) -> bool {
!arch::[<neon_mask_any_ $s>](value)
}
fn min_element(value: Storage<Self>) -> Self::Element {
unsafe { arch::[<vgetq_lane_ $s>]::<0>(value).min(arch::[<vgetq_lane_ $s>]::<1>(value)) }
}
fn max_element(value: Storage<Self>) -> Self::Element {
unsafe { arch::[<vgetq_lane_ $s>]::<0>(value).max(arch::[<vgetq_lane_ $s>]::<1>(value)) }
}
fn sum_elements(value: Storage<Self>) -> Self::Element {
unsafe { arch::[<vaddvq_ $s>](value) }
}
fn prod_elements(value: Storage<Self>) -> Self::Element {
unsafe {
arch::[<vgetq_lane_ $s>]::<0>(value).wrapping_mul(arch::[<vgetq_lane_ $s>]::<1>(value))
}
}
fn pairwise_sum(lo: Storage<Self>, hi: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vpaddq_ $s>](lo, hi) }
}
fn offset() -> Storage<Self> {
Self::splat(<Self::Lanes as Unsigned>::USIZE as $e)
}
fn indexed() -> Storage<Self> {
const INDEXED: Storage<$reg> = reg::<$reg, 2>([0, 1]);
INDEXED
}
fn add(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vaddq_ $s>](lhs, rhs) }
}
fn sub(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vsubq_ $s>](lhs, rhs) }
}
fn mul(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::neon_mullo_u64(arch::$to_u(lhs), arch::$to_u(rhs))) }
}
fn div(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
Self::zip(lhs, rhs, |a, b| if b == 0 { 0 } else { a.wrapping_div(b) })
}
fn rem(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
Self::zip(lhs, rhs, |a, b| if b == 0 { 0 } else { a.wrapping_rem(b) })
}
fn min(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
arch::$min(lhs, rhs)
}
fn max(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
arch::$max(lhs, rhs)
}
}
#[thermite_macros::inline_always]
impl IntegerRegister for $reg {
fn mulhi(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe {
let a0 = arch::[<vgetq_lane_ $s>]::<0>(lhs);
let a1 = arch::[<vgetq_lane_ $s>]::<1>(lhs);
let b0 = arch::[<vgetq_lane_ $s>]::<0>(rhs);
let b1 = arch::[<vgetq_lane_ $s>]::<1>(rhs);
let r = [
(((a0 as $w) * (b0 as $w)) >> 64) as $e,
(((a1 as $w) * (b1 as $w)) >> 64) as $e,
];
arch::[<vld1q_ $s>](r.as_ptr())
}
}
fn mullo(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
<Self as NumericRegister>::mul(lhs, rhs)
}
fn saturating_add(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vqaddq_ $s>](lhs, rhs) }
}
fn saturating_sub(lhs: Storage<Self>, rhs: Storage<Self>) -> Storage<Self> {
unsafe { arch::[<vqsubq_ $s>](lhs, rhs) }
}
fn div_branched(value: Storage<Self>, divider: crate::Divider<Self::Element>) -> Storage<Self> {
arch::[<div_ $div>]::<Self>(value, divider.multiplier(), divider.shift())
}
fn div_branchfree(value: Storage<Self>, divider: crate::BranchfreeDivider<Self::Element>) -> Storage<Self> {
arch::[<div_ $div _bf>]::<Self>(value, divider.multiplier(), divider.shift())
}
fn divv_branchfree(value: Storage<Self>, dividers: crate::divider::vector::VectorDivider<Self>) -> Storage<Self> {
arch::[<divv_ $div _bf>]::<Self>(value, dividers.multipliers.0, dividers.shifts.0)
}
const HAS_HARDWARE_POPCNT: bool = true;
fn count_ones(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::neon_popcnt_u64(arch::$to_u(value))) }
}
fn leading_zeros(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::neon_clz_u64(arch::$to_u(value))) }
}
fn trailing_zeros(value: Storage<Self>) -> Storage<Self> {
unsafe { arch::$from_u(arch::neon_ctz_u64(arch::$to_u(value))) }
}
}
}
};
}
macro_rules! neon_extend_scalar {
($reg:ty, elem: $e:ty) => {
#[thermite_macros::inline_always]
impl crate::register::ExtendRegister<$e> for $reg {
fn extend(value: Storage<$e>) -> Storage<Self> {
Self::single(value)
}
fn narrow(value: Storage<Self>) -> Storage<$e> {
Self::extract::<0>(value)
}
}
};
}
macro_rules! neon_widen_casts {
($narrow_reg:ty => [$wide_reg:ty; 2], suffixes: $s:ident/$w:ident) => {
paste::paste! {
#[thermite_macros::inline_always]
impl crate::register::CastRegister<$narrow_reg> for ArrayRegister<$wide_reg, 2> {
fn cast_from(value: Storage<$narrow_reg>) -> Storage<Self> {
unsafe {
ArrayRegister([
arch::[<vmovl_ $s>](arch::[<vget_low_ $s>](value)),
arch::[<vmovl_high_ $s>](value),
])
}
}
}
#[thermite_macros::inline_always]
impl crate::register::CastRegister<ArrayRegister<$wide_reg, 2>> for $narrow_reg {
fn cast_from(value: Storage<ArrayRegister<$wide_reg, 2>>) -> Storage<Self> {
unsafe { arch::[<vmovn_high_ $w>](arch::[<vmovn_ $w>](value.0[0]), value.0[1]) }
}
fn saturating_cast_from(value: Storage<ArrayRegister<$wide_reg, 2>>) -> Storage<Self> {
unsafe { arch::[<vqmovn_high_ $w>](arch::[<vqmovn_ $w>](value.0[0]), value.0[1]) }
}
}
}
};
}
macro_rules! neon_concat_scalar2 {
($reg:ty, elem: $e:ty, suffix: $s:ident) => {
paste::paste! {
#[thermite_macros::inline_always]
impl crate::register::ConcatRegister<$e> for $reg {
fn concat(lo: Storage<$e>, hi: Storage<$e>) -> Storage<Self> {
unsafe {
arch::[<vsetq_lane_ $s>]::<1>(hi, arch::[<vsetq_lane_ $s>]::<0>(lo, Self::EMPTY))
}
}
fn split(value: Storage<Self>) -> (Storage<$e>, Storage<$e>) {
unsafe {
(
arch::[<vgetq_lane_ $s>]::<0>(value),
arch::[<vgetq_lane_ $s>]::<1>(value),
)
}
}
}
}
};
}
macro_rules! neon_broadcast_align {
($dup:ident, $ext:ident; 2) => {
fn broadcast<const I: usize>(value: Storage<Self>) -> Storage<Self> {
unsafe {
match I {
0 => arch::$dup::<0>(value),
_ => arch::$dup::<1>(value),
}
}
}
const HAS_NATIVE_ALIGN: bool = true;
fn align<const OFFSET: usize>(a: Storage<Self>, b: Storage<Self>) -> Storage<Self> {
unsafe {
match OFFSET {
0 => a,
1 => arch::$ext::<1>(a, b),
2 => b,
_ => Self::swizzle_const::<crate::swizzle::AlignIndices<OFFSET, Self::Lanes>>(a, b),
}
}
}
};
($dup:ident, $ext:ident; 4) => {
fn broadcast<const I: usize>(value: Storage<Self>) -> Storage<Self> {
unsafe {
match I {
0 => arch::$dup::<0>(value),
1 => arch::$dup::<1>(value),
2 => arch::$dup::<2>(value),
_ => arch::$dup::<3>(value),
}
}
}
const HAS_NATIVE_ALIGN: bool = true;
fn align<const OFFSET: usize>(a: Storage<Self>, b: Storage<Self>) -> Storage<Self> {
unsafe {
match OFFSET {
0 => a,
1 => arch::$ext::<1>(a, b),
2 => arch::$ext::<2>(a, b),
3 => arch::$ext::<3>(a, b),
4 => b,
_ => Self::swizzle_const::<crate::swizzle::AlignIndices<OFFSET, Self::Lanes>>(a, b),
}
}
}
};
($dup:ident, $ext:ident; 8) => {
fn broadcast<const I: usize>(value: Storage<Self>) -> Storage<Self> {
unsafe {
match I {
0 => arch::$dup::<0>(value),
1 => arch::$dup::<1>(value),
2 => arch::$dup::<2>(value),
3 => arch::$dup::<3>(value),
4 => arch::$dup::<4>(value),
5 => arch::$dup::<5>(value),
6 => arch::$dup::<6>(value),
_ => arch::$dup::<7>(value),
}
}
}
const HAS_NATIVE_ALIGN: bool = true;
fn align<const OFFSET: usize>(a: Storage<Self>, b: Storage<Self>) -> Storage<Self> {
unsafe {
match OFFSET {
0 => a,
1 => arch::$ext::<1>(a, b),
2 => arch::$ext::<2>(a, b),
3 => arch::$ext::<3>(a, b),
4 => arch::$ext::<4>(a, b),
5 => arch::$ext::<5>(a, b),
6 => arch::$ext::<6>(a, b),
7 => arch::$ext::<7>(a, b),
8 => b,
_ => Self::swizzle_const::<crate::swizzle::AlignIndices<OFFSET, Self::Lanes>>(a, b),
}
}
}
};
($dup:ident, $ext:ident; 16) => {
fn broadcast<const I: usize>(value: Storage<Self>) -> Storage<Self> {
unsafe {
match I {
0 => arch::$dup::<0>(value),
1 => arch::$dup::<1>(value),
2 => arch::$dup::<2>(value),
3 => arch::$dup::<3>(value),
4 => arch::$dup::<4>(value),
5 => arch::$dup::<5>(value),
6 => arch::$dup::<6>(value),
7 => arch::$dup::<7>(value),
8 => arch::$dup::<8>(value),
9 => arch::$dup::<9>(value),
10 => arch::$dup::<10>(value),
11 => arch::$dup::<11>(value),
12 => arch::$dup::<12>(value),
13 => arch::$dup::<13>(value),
14 => arch::$dup::<14>(value),
_ => arch::$dup::<15>(value),
}
}
}
const HAS_NATIVE_ALIGN: bool = true;
fn align<const OFFSET: usize>(a: Storage<Self>, b: Storage<Self>) -> Storage<Self> {
unsafe {
match OFFSET {
0 => a,
1 => arch::$ext::<1>(a, b),
2 => arch::$ext::<2>(a, b),
3 => arch::$ext::<3>(a, b),
4 => arch::$ext::<4>(a, b),
5 => arch::$ext::<5>(a, b),
6 => arch::$ext::<6>(a, b),
7 => arch::$ext::<7>(a, b),
8 => arch::$ext::<8>(a, b),
9 => arch::$ext::<9>(a, b),
10 => arch::$ext::<10>(a, b),
11 => arch::$ext::<11>(a, b),
12 => arch::$ext::<12>(a, b),
13 => arch::$ext::<13>(a, b),
14 => arch::$ext::<14>(a, b),
15 => arch::$ext::<15>(a, b),
16 => b,
_ => Self::swizzle_const::<crate::swizzle::AlignIndices<OFFSET, Self::Lanes>>(a, b),
}
}
}
};
}
macro_rules! neon_approx_recip {
(yes, $s:ident) => {
paste::paste! {
const HAS_APPROX_RCP: bool = cfg!(not(feature = "strict_ieee754"));
const HAS_APPROX_RSQRT: bool = cfg!(not(feature = "strict_ieee754"));
fn rcp(value: Storage<Self>) -> Storage<Self> {
cfg_select! {
feature = "strict_ieee754" => {
Self::div(Self::ONE, value)
}
_ => arch::[<neon_rcp_ $s>](value),
}
}
fn rsqrt(value: Storage<Self>) -> Storage<Self> {
cfg_select! {
feature = "strict_ieee754" => {
Self::rcp(Self::sqrt(value))
}
_ => arch::[<neon_rsqrt_ $s>](value),
}
}
}
};
(no, $s:ident) => {
const HAS_APPROX_RCP: bool = false;
const HAS_APPROX_RSQRT: bool = false;
};
}
#[rustfmt::skip]
macro_rules! impl_widen_indices_neon {
($($reg:ty => ($shape:ident, $sfx:ident)),* $(,)?) => {
$(
#[thermite_macros::inline_always]
impl $crate::register::WidenIndexRegister for $reg {
fn widen_indices(
idxs: &generic_array::GenericArray<u8, generic_array::typenum::U8>,
) -> generic_array::GenericArray<u32, <Self as $crate::register::CoreRegister>::Lanes> {
unsafe { impl_widen_indices_neon!(@body idxs, $shape) }
}
fn permutev_row(
value: $crate::register::Storage<Self>,
row: &generic_array::GenericArray<u8, generic_array::typenum::U8>,
) -> $crate::register::Storage<Self> {
paste::paste! {
unsafe { arch::[<neon_tbl_ $sfx>](value, impl_widen_indices_neon!(@row row, $shape)) }
}
}
}
)*
};
(@row $row:ident, x2) => { arch::neon_lane_table_row::<2>($row.as_ptr()) };
(@row $row:ident, x4) => { arch::neon_lane_table_row::<4>($row.as_ptr()) };
(@row $row:ident, x8) => { arch::neon_lane_table_row::<8>($row.as_ptr()) };
(@body $idxs:ident, x2) => {{ impl_widen_indices_neon!(@low $idxs) }};
(@body $idxs:ident, x4) => {{ impl_widen_indices_neon!(@low $idxs) }};
(@body $idxs:ident, x8) => {{
let w16 = arch::vmovl_u8(arch::vld1_u8($idxs.as_ptr()));
let lo = arch::vmovl_u16(arch::vget_low_u16(w16));
let hi = arch::vmovl_high_u16(w16);
core::mem::transmute_copy(&[lo, hi])
}};
(@low $idxs:ident) => {{
let w16 = arch::vmovl_u8(arch::vld1_u8($idxs.as_ptr()));
core::mem::transmute_copy(&arch::vmovl_u16(arch::vget_low_u16(w16)))
}};
}