use super::{Crt, Lanes, Prime};
use core::arch::aarch64::*;
use core::arch::asm;
pub(in super::super) const FFT_MUL: usize = 360;
pub(in super::super) const FFT_MUL_UNBALANCED: usize = 180;
pub(in super::super) const FFT_SQR: usize = 380;
pub(super) type V = Neon;
#[derive(Clone, Copy)]
pub(super) struct Neon(int32x4_t);
impl Lanes for Neon {
type C = int32x4_t;
#[inline(always)]
fn consts(p: &Prime) -> int32x4_t {
let c = [p.q, p.v, p.qinv, 0];
unsafe { vld1q_s32(c.as_ptr()) }
}
#[inline(always)]
unsafe fn load(p: *const i32) -> Self {
unsafe { Neon(vld1q_s32(p)) }
}
#[inline(always)]
unsafe fn store(self, p: *mut i32) {
unsafe { vst1q_s32(p, self.0) }
}
#[inline(always)]
unsafe fn load2(p: *const i32) -> (Self, Self) {
let x = unsafe { vld2q_s32(p) };
(Neon(x.0), Neon(x.1))
}
#[inline(always)]
unsafe fn load4(p: *const i32) -> (Self, Self, Self, Self) {
let x = unsafe { vld4q_s32(p) };
(Neon(x.0), Neon(x.1), Neon(x.2), Neon(x.3))
}
#[inline(always)]
unsafe fn store2(a: Self, b: Self, p: *mut i32) {
unsafe { vst2q_s32(p, int32x4x2_t(a.0, b.0)) }
}
#[inline(always)]
unsafe fn pair(p: *const i32) -> Self {
unsafe { Neon(vcombine_s32(vld1_s32(p), vdup_n_s32(0))) }
}
#[inline(always)]
fn splat(x: i32) -> Self {
unsafe { Neon(vdupq_n_s32(x)) }
}
#[inline(always)]
fn add(self, o: Self) -> Self {
unsafe { Neon(vaddq_s32(self.0, o.0)) }
}
#[inline(always)]
fn sub(self, o: Self) -> Self {
unsafe { Neon(vsubq_s32(self.0, o.0)) }
}
#[inline(always)]
fn mul(self, w: Self, wp: Self, c: int32x4_t) -> Self {
let r: int32x4_t;
unsafe {
asm!(
"sqrdmulh {t:v}.4s, {a:v}.4s, {wp:v}.4s",
"mul {r:v}.4s, {a:v}.4s, {w:v}.4s",
"mls {r:v}.4s, {t:v}.4s, {c:v}.s[0]",
a = in(vreg) self.0,
w = in(vreg) w.0,
wp = in(vreg) wp.0,
c = in(vreg_low16) c,
t = out(vreg) _,
r = out(vreg) r,
options(pure, nomem, nostack, preserves_flags),
);
}
Neon(r)
}
#[inline(always)]
fn mul_lanes<const LW: i32, const LP: i32>(self, v: Self, c: int32x4_t) -> Self {
let r: int32x4_t;
unsafe {
asm!(
"sqrdmulh {t:v}.4s, {a:v}.4s, {v:v}.s[{lp}]",
"mul {r:v}.4s, {a:v}.4s, {v:v}.s[{lw}]",
"mls {r:v}.4s, {t:v}.4s, {c:v}.s[0]",
a = in(vreg) self.0,
v = in(vreg_low16) v.0,
c = in(vreg_low16) c,
lw = const LW,
lp = const LP,
t = out(vreg) _,
r = out(vreg) r,
options(pure, nomem, nostack, preserves_flags),
);
}
Neon(r)
}
#[inline(always)]
fn reduce(self, c: int32x4_t) -> Self {
unsafe {
let t = vrshrq_n_s32::<28>(vqdmulhq_laneq_s32::<1>(self.0, c));
Neon(vmlsq_laneq_s32::<0>(self.0, t, c))
}
}
#[inline(always)]
fn mont(self, o: Self, c: int32x4_t) -> Self {
unsafe {
let lo = vmulq_s32(self.0, o.0);
let hi = vqdmulhq_s32(self.0, o.0);
let m = vmulq_laneq_s32::<2>(lo, c);
Neon(vhsubq_s32(hi, vqdmulhq_laneq_s32::<0>(m, c)))
}
}
#[inline(always)]
fn transpose(v: [Self; 4]) -> [Self; 4] {
unsafe {
let a0 = vreinterpretq_s64_s32(vtrn1q_s32(v[0].0, v[1].0));
let a1 = vreinterpretq_s64_s32(vtrn2q_s32(v[0].0, v[1].0));
let a2 = vreinterpretq_s64_s32(vtrn1q_s32(v[2].0, v[3].0));
let a3 = vreinterpretq_s64_s32(vtrn2q_s32(v[2].0, v[3].0));
[
Neon(vreinterpretq_s32_s64(vtrn1q_s64(a0, a2))),
Neon(vreinterpretq_s32_s64(vtrn1q_s64(a1, a3))),
Neon(vreinterpretq_s32_s64(vtrn2q_s64(a0, a2))),
Neon(vreinterpretq_s32_s64(vtrn2q_s64(a1, a3))),
]
}
}
#[inline(always)]
fn normalize(self, c: int32x4_t) -> Self {
unsafe {
let negative = vshrq_n_s32::<31>(self.0);
Neon(vaddq_s32(
self.0,
vandq_s32(negative, vdupq_laneq_s32::<0>(c)),
))
}
}
#[inline(always)]
fn constants(self, f: f64) -> Self {
unsafe {
let f = vdupq_n_f64(f);
let lo = vcvtq_f64_s64(vmovl_s32(vget_low_s32(self.0)));
let hi = vcvtq_f64_s64(vmovl_high_s32(self.0));
let lo = vcvtnq_s64_f64(vmulq_f64(lo, f));
let hi = vcvtnq_s64_f64(vmulq_f64(hi, f));
Neon(vmovn_high_s64(vmovn_s64(lo), hi))
}
}
#[inline(always)]
fn rev(self) -> Self {
unsafe {
let r = vrev64q_s32(self.0);
Neon(vextq_s32::<2>(r, r))
}
}
#[inline(always)]
fn neg(self) -> Self {
unsafe { Neon(vnegq_s32(self.0)) }
}
}
#[inline(always)]
pub(super) unsafe fn four<const ND: usize>(
res: &[*const i32; 8],
j: usize,
c: &Crt,
out: &mut [[u64; 8]; 4],
o: usize,
) {
unsafe {
let z = vdupq_n_u64(0);
let (mut lo, mut hi) = ([z; 8], [z; 8]);
let (mut flo, mut fhi) = (z, z);
for (i, r) in res.iter().enumerate().take(c.np) {
let y = vld1q_u32(r.add(j).cast());
let yl = vget_low_u32(y);
let m0 = vld1q_u32(c.m[i].as_ptr());
let m1 = vld1q_u32(c.m[i].as_ptr().add(4));
flo = vmlal_n_u32(flo, yl, c.ci[i]);
fhi = vmlal_high_n_u32(fhi, y, c.ci[i]);
macro_rules! digit {
($d:literal, $m:ident, $l:literal) => {
if $d < ND {
lo[$d] = vmlal_laneq_u32::<$l>(lo[$d], yl, $m);
hi[$d] = vmlal_high_laneq_u32::<$l>(hi[$d], y, $m);
}
};
}
digit!(0, m0, 0);
digit!(1, m0, 1);
digit!(2, m0, 2);
digit!(3, m0, 3);
digit!(4, m1, 0);
digit!(5, m1, 1);
digit!(6, m1, 2);
digit!(7, m1, 3);
}
let bias = vdupq_n_u64(1 << 58);
let kl = vmovn_u64(vshrq_n_u64::<60>(vaddq_u64(flo, bias)));
let kh = vmovn_u64(vshrq_n_u64::<60>(vaddq_u64(fhi, bias)));
let p0 = vld1q_u32(c.pneg.as_ptr());
let p1 = vld1q_u32(c.pneg.as_ptr().add(4));
macro_rules! times_k {
($d:literal, $m:ident, $l:literal) => {
if $d < ND {
lo[$d] = vmlal_laneq_u32::<$l>(lo[$d], kl, $m);
hi[$d] = vmlal_laneq_u32::<$l>(hi[$d], kh, $m);
}
};
}
times_k!(0, p0, 0);
times_k!(1, p0, 1);
times_k!(2, p0, 2);
times_k!(3, p0, 3);
times_k!(4, p1, 0);
times_k!(5, p1, 1);
times_k!(6, p1, 2);
times_k!(7, p1, 3);
let mut digits = [vdupq_n_u32(0); 9];
for d in 0..ND {
if d + 1 < ND {
lo[d + 1] = vsraq_n_u64::<32>(lo[d + 1], lo[d]);
hi[d + 1] = vsraq_n_u64::<32>(hi[d + 1], hi[d]);
}
digits[d] = vcombine_u32(vmovn_u64(lo[d]), vmovn_u64(hi[d]));
}
for e in 0..ND.div_ceil(2) {
let (a, b) = (digits[2 * e], digits[2 * e + 1]);
let row = out[e].as_mut_ptr().add(o);
vst1q_u64(row, vreinterpretq_u64_u32(vzip1q_u32(a, b)));
vst1q_u64(row.add(2), vreinterpretq_u64_u32(vzip2q_u32(a, b)));
}
}
}