#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
target_pointer_width = "64",
any(
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "windows",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "fuchsia"
),
not(miri)
))]
#[path = "fft/aarch64.rs"]
mod arch;
#[cfg(not(all(
target_arch = "aarch64",
target_endian = "little",
target_pointer_width = "64",
any(
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "windows",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "fuchsia"
),
not(miri)
)))]
#[path = "fft/portable.rs"]
mod arch;
use arch::{V, four};
pub(super) use arch::{FFT_MUL, FFT_MUL_UNBALANCED, FFT_SQR};
const PRIMES: [(u32, u32); 8] = [
(469_762_049, 3),
(463_470_593, 3),
(459_276_289, 11),
(415_236_097, 5),
(387_973_121, 6),
(383_778_817, 5),
(377_487_361, 7),
(274_726_913, 3),
];
const MAX_K: u32 = 21;
const MIN_K: u32 = 5;
const fn pow_mod(b: u64, mut e: u64, q: u64) -> u64 {
let (mut r, mut x) = (1, b % q);
while e > 0 {
if e & 1 == 1 {
r = r * x % q;
}
x = x * x % q;
e >>= 1;
}
r
}
const fn centred(x: u64, q: u64) -> i32 {
let x = x % q;
if x > q / 2 {
(x as i64 - q as i64) as i32
} else {
x as i32
}
}
const fn barrett(w: i32, q: u32) -> i32 {
let (x, q) = ((w as i64).unsigned_abs() << 31, q as u64);
let r = ((x + q / 2) / q) as i64;
(if w < 0 { -r } else { r }) as i32
}
#[derive(Clone, Copy)]
pub(super) struct Prime {
pub(super) q: i32,
pub(super) qinv: i32,
pub(super) v: i32,
}
struct PrimeData {
prime: Prime,
roots: [(i32, i32); MAX_K as usize + 1],
inv2k: [i32; MAX_K as usize + 1],
pow2: [(i32, i32); 112],
}
const fn prime_data(q: u32, g: u32) -> PrimeData {
let qq = q as u64;
let mut inv: u32 = 1;
let mut i = 0;
while i < 5 {
inv = inv.wrapping_mul(2u32.wrapping_sub(q.wrapping_mul(inv)));
i += 1;
}
let prime = Prime {
q: q as i32,
qinv: inv as i32,
v: (((1u64 << 59) + qq / 2) / qq) as i32,
};
let root = pow_mod(g as u64, (qq - 1) >> MAX_K, qq);
let mut roots = [(0, 0); MAX_K as usize + 1];
let mut inv2k = [0; MAX_K as usize + 1];
let mut j = 0;
while j <= MAX_K as usize {
let w = centred(pow_mod(root, 1 << (MAX_K as usize - j), qq), qq);
roots[j] = (w, barrett(w, q));
inv2k[j] = centred(pow_mod(qq.div_ceil(2), j as u64, qq), qq);
j += 1;
}
let mut pow2 = [(0, 0); 112];
let mut e = 0;
while e < 112 {
let w = centred(pow_mod(2, e as u64, qq), qq);
pow2[e] = (w, barrett(w, q));
e += 1;
}
PrimeData {
prime,
roots,
inv2k,
pow2,
}
}
static PRIME_DATA: [PrimeData; 8] = {
let mut i = 0;
let mut data = [const { prime_data(3, 2) }; 8];
while i < 8 {
data[i] = prime_data(PRIMES[i].0, PRIMES[i].1);
i += 1;
}
data
};
pub(super) struct Crt {
pub(super) np: usize,
pub(super) nd: usize,
pub(super) m: [[u32; 8]; 8],
pub(super) pneg: [u32; 8],
pub(super) ci: [u32; 8],
mu: [i32; 8],
bits: u32,
}
const fn mul_digits(a: [u32; 8], b: u32) -> [u32; 8] {
let mut r = [0; 8];
let mut carry = 0u64;
let mut i = 0;
while i < 8 {
let t = a[i] as u64 * b as u64 + carry;
r[i] = t as u32;
carry = t >> 32;
i += 1;
}
r
}
const fn crt_data(np: usize) -> Crt {
let mut p = [0u32; 8];
p[0] = 1;
let mut i = 0;
while i < np {
p = mul_digits(p, PRIMES[i].0);
i += 1;
}
let mut top = 7;
while p[top] == 0 {
top -= 1;
}
let bits = 32 * top as u32 + 32 - p[top].leading_zeros();
let nd = bits.div_ceil(32) as usize;
let mut pneg = [0u32; 8];
let mut carry = 1u64;
let mut d = 0;
while d < nd {
let t = (!p[d]) as u64 + carry;
pneg[d] = t as u32;
carry = t >> 32;
d += 1;
}
let mut m = [[0u32; 8]; 8];
let mut ci = [0u32; 8];
let mut mu = [0i32; 8];
i = 0;
while i < np {
let q = PRIMES[i].0 as u64;
let mut v = [0u32; 8];
v[0] = 1;
let mut rem = 1u64;
let mut j = 0;
while j < np {
if j != i {
v = mul_digits(v, PRIMES[j].0);
rem = rem * (PRIMES[j].0 as u64 % q) % q;
}
j += 1;
}
d = nd;
while d < 8 {
v[d] = 0;
d += 1;
}
m[i] = v;
ci[i] = ((1u64 << 60) / q) as u32;
mu[i] = centred(pow_mod(rem, q - 2, q) * ((1u64 << 32) % q), q);
i += 1;
}
Crt {
np,
nd,
m,
pneg,
ci,
mu,
bits,
}
}
static CRT: [Crt; 9] = {
let mut c = [const { crt_data(2) }; 9];
let mut np = 4;
while np <= 8 {
c[np] = crt_data(np);
np += 1;
}
c
};
pub(super) trait Lanes: Copy {
type C: Copy;
fn consts(p: &Prime) -> Self::C;
unsafe fn load(p: *const i32) -> Self;
unsafe fn store(self, p: *mut i32);
unsafe fn load2(p: *const i32) -> (Self, Self);
unsafe fn load4(p: *const i32) -> (Self, Self, Self, Self);
unsafe fn store2(a: Self, b: Self, p: *mut i32);
unsafe fn pair(p: *const i32) -> Self;
fn splat(x: i32) -> Self;
fn add(self, o: Self) -> Self;
fn sub(self, o: Self) -> Self;
fn mul(self, w: Self, wp: Self, c: Self::C) -> Self;
fn mul_lanes<const LW: i32, const LP: i32>(self, v: Self, c: Self::C) -> Self;
fn reduce(self, c: Self::C) -> Self;
fn mont(self, o: Self, c: Self::C) -> Self;
fn transpose(v: [Self; 4]) -> [Self; 4];
fn normalize(self, c: Self::C) -> Self;
fn constants(self, f: f64) -> Self;
fn rev(self) -> Self;
fn neg(self) -> Self;
}
unsafe fn twiddles<S: Lanes>(t: *mut i32, k: u32, d: &PrimeData) {
let half = 1usize << (k - 1);
let (q, c) = (d.prime.q as i64, S::consts(&d.prime));
let f = (1u64 << 31) as f64 / q as f64;
unsafe {
let (fw, inv) = (t, t.add(2 * half));
let (w4, w8) = (d.roots[2].0 as i64, d.roots[3].0 as i64);
for (i, z) in [1, w4, w8, w4 * w8].into_iter().enumerate() {
let z = centred(z.rem_euclid(q) as u64, q as u64);
*fw.add(2 * i) = z;
*fw.add(2 * i + 1) = barrett(z, q as u32);
}
let mut i = 2;
while (1usize << i) < half {
let (w, wp) = d.roots[i + 2];
let (w, wp) = (S::splat(w), S::splat(wp));
let n = 1usize << i;
for j in (0..n).step_by(4) {
let (z, _) = S::load2(fw.add(2 * j));
let z = z.mul(w, wp, c);
S::store2(z, z.constants(f), fw.add(2 * (n + j)));
}
i += 1;
}
*inv = *fw;
*inv.add(1) = *fw.add(1);
for (b, m) in [(1, 1), (2, 3), (3, 2)] {
*inv.add(2 * b) = -*fw.add(2 * m);
*inv.add(2 * b + 1) = -*fw.add(2 * m + 1);
}
let mut n = 4;
while n < half {
for j in (0..n).step_by(4) {
let (z, zp) = S::load2(fw.add(2 * (2 * n - 4 - j)));
S::store2(z.rev().neg(), zp.rev().neg(), inv.add(2 * (n + j)));
}
n *= 2;
}
}
}
#[inline(always)]
unsafe fn fwd4<S: Lanes>(a: *mut i32, l: usize, s: u32, w: *const i32, c: S::C) {
let m = l >> s;
let qr = m >> 2;
unsafe {
for b in 0..(1usize << s) {
let (z1, z23) = (S::pair(w.add(2 * b)), S::load(w.add(4 * b)));
let base = a.add(b * m);
for j in (0..qr).step_by(4) {
let p0 = base.add(j);
let a0 = S::load(p0).reduce(c);
let a1 = S::load(p0.add(qr));
let t2 = S::load(p0.add(2 * qr)).mul_lanes::<0, 1>(z1, c);
let t3 = S::load(p0.add(3 * qr)).mul_lanes::<0, 1>(z1, c);
let (b0, b2) = (a0.add(t2), a0.sub(t2));
let u1 = a1.add(t3).mul_lanes::<0, 1>(z23, c);
let u3 = a1.sub(t3).mul_lanes::<2, 3>(z23, c);
b0.add(u1).store(p0);
b0.sub(u1).store(p0.add(qr));
b2.add(u3).store(p0.add(2 * qr));
b2.sub(u3).store(p0.add(3 * qr));
}
}
}
}
#[inline(always)]
unsafe fn fwd_last4<S: Lanes>(a: *mut i32, l: usize, w: *const i32, c: S::C) {
unsafe {
for b in 0..(l >> 4) {
let p0 = a.add(16 * b);
let (z1, z23) = (S::pair(w.add(2 * b)), S::load(w.add(4 * b)));
let a0 = S::load(p0).reduce(c);
let a1 = S::load(p0.add(4));
let t2 = S::load(p0.add(8)).mul_lanes::<0, 1>(z1, c);
let t3 = S::load(p0.add(12)).mul_lanes::<0, 1>(z1, c);
let (b0, b2) = (a0.add(t2), a0.sub(t2));
let u1 = a1.add(t3).mul_lanes::<0, 1>(z23, c);
let u3 = a1.sub(t3).mul_lanes::<2, 3>(z23, c);
let [t0, t1, t2, t3] = S::transpose([b0.add(u1), b0.sub(u1), b2.add(u3), b2.sub(u3)]);
let t0 = t0.reduce(c);
let (w2, w2p) = S::load2(w.add(8 * b));
let x2 = t2.mul(w2, w2p, c);
let x3 = t3.mul(w2, w2p, c);
let (y0, y2) = (t0.add(x2), t0.sub(x2));
let (we, wpe, wo, wpo) = S::load4(w.add(16 * b));
let z1 = t1.add(x3).mul(we, wpe, c);
let z3 = t1.sub(x3).mul(wo, wpo, c);
y0.add(z1).store(p0);
y0.sub(z1).store(p0.add(4));
y2.add(z3).store(p0.add(8));
y2.sub(z3).store(p0.add(12));
}
}
}
#[inline(always)]
unsafe fn inv4<S: Lanes>(a: *mut i32, l: usize, s: u32, w: *const i32, c: S::C) {
let m = l >> s;
let qr = m >> 2;
unsafe {
for b in 0..(1usize << s) {
let (z1, z23) = (S::pair(w.add(2 * b)), S::load(w.add(4 * b)));
let base = a.add(b * m);
for j in (0..qr).step_by(4) {
let p0 = base.add(j);
let a0 = S::load(p0);
let a1 = S::load(p0.add(qr));
let a2 = S::load(p0.add(2 * qr));
let a3 = S::load(p0.add(3 * qr));
let b0 = a0.add(a1).reduce(c);
let b1 = a0.sub(a1).mul_lanes::<0, 1>(z23, c);
let b2 = a2.add(a3).reduce(c);
let b3 = a2.sub(a3).mul_lanes::<2, 3>(z23, c);
b0.add(b2).store(p0);
b1.add(b3).store(p0.add(qr));
b0.sub(b2).mul_lanes::<0, 1>(z1, c).store(p0.add(2 * qr));
b1.sub(b3).mul_lanes::<0, 1>(z1, c).store(p0.add(3 * qr));
}
}
}
}
#[inline(always)]
unsafe fn inv_last4<S: Lanes>(a: *mut i32, l: usize, w: *const i32, c: S::C) {
unsafe {
for b in 0..(l >> 4) {
let p0 = a.add(16 * b);
let (y0, y1) = (S::load(p0), S::load(p0.add(4)));
let (y2, y3) = (S::load(p0.add(8)), S::load(p0.add(12)));
let (we, wpe, wo, wpo) = S::load4(w.add(16 * b));
let x0 = y0.add(y1).reduce(c);
let x1 = y0.sub(y1).mul(we, wpe, c);
let x2 = y2.add(y3).reduce(c);
let x3 = y2.sub(y3).mul(wo, wpo, c);
let (w2, w2p) = S::load2(w.add(8 * b));
let t2 = x0.sub(x2).mul(w2, w2p, c);
let t3 = x1.sub(x3).mul(w2, w2p, c);
let [v0, v1, v2, v3] = S::transpose([x0.add(x2), x1.add(x3), t2, t3]);
let (z1, z23) = (S::pair(w.add(2 * b)), S::load(w.add(4 * b)));
let b0 = v0.add(v1).reduce(c);
let b1 = v0.sub(v1).mul_lanes::<0, 1>(z23, c);
let b2 = v2.add(v3).reduce(c);
let b3 = v2.sub(v3).mul_lanes::<2, 3>(z23, c);
b0.add(b2).store(p0);
b1.add(b3).store(p0.add(4));
b0.sub(b2).mul_lanes::<0, 1>(z1, c).store(p0.add(8));
b1.sub(b3).mul_lanes::<0, 1>(z1, c).store(p0.add(12));
}
}
}
fn split_levels(k: u32) -> u32 {
if k.is_multiple_of(2) { 2 } else { 1 }
}
unsafe fn forward<S: Lanes>(a: *mut i32, k: u32, t: *const i32, c: S::C) {
let l = 1usize << k;
let mut s = split_levels(k);
unsafe {
while s + 4 < k {
fwd4::<S>(a, l, s, t, c);
s += 2;
}
fwd_last4::<S>(a, l, t, c);
}
}
unsafe fn inverse<S: Lanes>(a: *mut i32, k: u32, w: *const i32, c: S::C) {
let l = 1usize << k;
unsafe {
inv_last4::<S>(a, l, w, c);
let mut s = k as i32 - 6;
while s >= split_levels(k) as i32 {
inv4::<S>(a, l, s as u32, w, c);
s -= 2;
}
}
}
unsafe fn pointwise<S: Lanes>(a: *mut i32, b: *const i32, l: usize, c: S::C) {
unsafe {
for i in (0..l).step_by(4) {
S::load(a.add(i)).mont(S::load(b.add(i)), c).store(a.add(i));
}
}
}
#[inline(always)]
unsafe fn residue<S: Lanes>(
d: *const i32,
j: usize,
stride: usize,
dn: usize,
r: &[(i32, i32); 4],
c: S::C,
) -> S {
unsafe {
let mut x = S::load(d.add(j));
for (t, &(w, wp)) in r.iter().enumerate().take(dn).skip(1) {
x = x.add(S::load(d.add(t * stride + j)).mul(S::splat(w), S::splat(wp), c));
}
x
}
}
#[allow(clippy::too_many_arguments)]
unsafe fn split<S: Lanes>(
a: *mut i32,
k: u32,
d: *const i32,
stride: usize,
nc: usize,
dn: usize,
r: &[(i32, i32); 4],
t: *const i32,
c: S::C,
) {
let top = nc.next_multiple_of(4);
let zero = S::splat(0);
unsafe {
let res = |j: usize| residue::<S>(d, j, stride, dn, r, c);
if split_levels(k) == 1 {
let h = 1usize << (k - 1);
for j in (0..h).step_by(4) {
let (u, v) = if j + h < top {
let (u, v) = (res(j).reduce(c), res(j + h).reduce(c));
(u.add(v), u.sub(v))
} else if j < top {
let u = res(j);
(u, u)
} else {
(zero, zero)
};
u.store(a.add(j));
v.store(a.add(j + h));
}
return;
}
let qq = 1usize << (k - 2);
let z1 = S::pair(t.add(2));
for j in (0..qq).step_by(4) {
let quarter = |i: usize| {
if j + i * qq < top {
res(j + i * qq).reduce(c)
} else {
zero
}
};
let (r0, r1) = (quarter(0), quarter(1));
let (u0, u1, u2, u3) = if j + 2 * qq < top {
let (r2, r3) = (quarter(2), quarter(3));
(r0.add(r2), r1.add(r3), r0.sub(r2), r1.sub(r3))
} else {
(r0, r1, r0, r1)
};
let v3 = u3.mul_lanes::<0, 1>(z1, c);
u0.add(u1).store(a.add(j));
u0.sub(u1).store(a.add(j + qq));
u2.add(v3).store(a.add(j + 2 * qq));
u2.sub(v3).store(a.add(j + 3 * qq));
}
}
}
unsafe fn finish<S: Lanes>(a: *mut i32, k: u32, n: usize, mu: (i32, i32), w: *const i32, c: S::C) {
let (m, mp) = (S::splat(mu.0), S::splat(mu.1));
let scale = |x: S| x.mul(m, mp, c).normalize(c);
unsafe {
if split_levels(k) == 1 {
let h = 1usize << (k - 1);
for j in (0..h.min(n.next_multiple_of(4))).step_by(4) {
let (u, v) = (S::load(a.add(j)), S::load(a.add(j + h)));
scale(u.add(v)).store(a.add(j));
if j + h < n {
scale(u.sub(v)).store(a.add(j + h));
}
}
return;
}
let qq = 1usize << (k - 2);
let iz1 = S::pair(w.add(2));
for j in (0..qq.min(n.next_multiple_of(4))).step_by(4) {
let a0 = S::load(a.add(j));
let a1 = S::load(a.add(j + qq));
let a2 = S::load(a.add(j + 2 * qq));
let a3 = S::load(a.add(j + 3 * qq));
let b0 = a0.add(a1).reduce(c);
let b1 = a0.sub(a1).reduce(c);
let b2 = a2.add(a3).reduce(c);
let b3 = a2.sub(a3).mul_lanes::<0, 1>(iz1, c);
scale(b0.add(b2)).store(a.add(j));
if j + qq < n {
scale(b1.add(b3)).store(a.add(j + qq));
}
if j + 2 * qq < n {
scale(b0.sub(b2)).store(a.add(j + 2 * qq));
}
if j + 3 * qq < n {
scale(b1.sub(b3)).store(a.add(j + 3 * qq));
}
}
}
}
unsafe fn digits(d: *mut i32, stride: usize, x: *const u64, n: usize, cf: Config, nc: usize) {
unsafe {
let get = |pos: usize, len: usize| -> i32 {
let (o, sh) = (pos / 64, pos % 64);
if o >= n {
return 0;
}
let mut v = *x.add(o) >> sh;
if sh + len > 64 && o + 1 < n {
v |= *x.add(o + 1) << (64 - sh);
}
(v & ((1 << len) - 1)) as i32
};
#[cfg(target_endian = "little")]
let start = digits_le(d, stride, x, n, cf, nc);
#[cfg(not(target_endian = "little"))]
let start = 0;
for j in start..stride {
for t in 0..cf.dn {
let lo = t * cf.b;
let len = cf.b.min(cf.w - lo);
*d.add(t * stride + j) = if j < nc { get(j * cf.w + lo, len) } else { 0 };
}
}
}
}
#[cfg(target_endian = "little")]
unsafe fn digits_le(
d: *mut i32,
stride: usize,
x: *const u64,
n: usize,
cf: Config,
nc: usize,
) -> usize {
#[inline(always)]
unsafe fn run<const W: usize, const B: usize, const DN: usize>(
d: *mut i32,
stride: usize,
x: *const u8,
safe: usize,
) {
unsafe {
for j in 0..safe {
let base = x.add(j * (W / 8));
for t in 0..DN {
let lo = t * B;
let len = if B < W - lo { B } else { W - lo };
let v = u64::from_le_bytes(core::ptr::read_unaligned(base.add(lo / 8).cast()));
*d.add(t * stride + j) = ((v >> (lo % 8)) & ((1 << len) - 1)) as i32;
}
}
}
}
let (bytes, last) = (8 * n, (cf.dn - 1) * cf.b / 8);
let safe = if bytes >= 8 + last {
((bytes - 8 - last) * 8 / cf.w + 1).min(nc)
} else {
0
};
let x = x.cast::<u8>();
unsafe {
match (cf.w, cf.b, cf.dn) {
(48, 24, 2) => run::<48, 24, 2>(d, stride, x, safe),
(56, 28, 2) => run::<56, 28, 2>(d, stride, x, safe),
(64, 22, 3) => run::<64, 22, 3>(d, stride, x, safe),
(72, 24, 3) => run::<72, 24, 3>(d, stride, x, safe),
(88, 22, 4) => run::<88, 22, 4>(d, stride, x, safe),
(104, 26, 4) => run::<104, 26, 4>(d, stride, x, safe),
_ => return 0,
}
}
safe
}
#[inline(always)]
fn add_coefficient<const W: usize, const ND: usize, const NL: usize>(
acc: &mut [u128; 24],
c: &[[u64; 8]; 4],
t: usize,
) {
let pos = t * W;
let (a, s) = (pos / 64, (pos % 64) as u32);
for i in 0..=NL {
if i == NL && (s == 0 || 32 * ND as u32 + s <= 64 * NL as u32) {
break;
}
let lo = if i < NL { c[i][t] << s } else { 0 };
let hi = if s > 0 && i > 0 {
c[i - 1][t] >> (64 - s)
} else {
0
};
acc[a + i] += u128::from(lo | hi);
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn group<const W: usize, const ND: usize, const NL: usize>(
r: *mut u64,
rn: usize,
o: usize,
res: &[*const i32; 8],
j: usize,
n: usize,
crt: &Crt,
pend: &mut [u128; 8],
carry: &mut u128,
) {
let mut limbs = [[0u64; 8]; 4];
unsafe {
four::<ND>(res, j, crt, &mut limbs, 0);
four::<ND>(res, j + 4, crt, &mut limbs, 4);
}
for t in n.saturating_sub(j).min(8)..8 {
for l in limbs.iter_mut() {
l[t] = 0;
}
}
let mut acc = [0u128; 24];
acc[..8].copy_from_slice(pend);
for t in 0..8 {
add_coefficient::<W, ND, NL>(&mut acc, &limbs, t);
}
let (gl, span) = (W / 8, (7 * W) / 64 + NL + 1);
for (e, &v) in acc.iter().enumerate().take(gl) {
let s = v + *carry;
if o + e < rn {
unsafe { *r.add(o + e) = s as u64 };
}
*carry = s >> 64;
}
for (e, p) in pend.iter_mut().enumerate() {
*p = if gl + e < span { acc[gl + e] } else { 0 };
}
}
#[inline(never)]
unsafe fn crt<const W: usize, const ND: usize, const NL: usize>(
r: *mut u64,
rn: usize,
res: *const i32,
l: usize,
n: usize,
c: &Crt,
) -> [u64; 2] {
unsafe {
let ptrs: [*const i32; 8] = core::array::from_fn(|i| res.add(i.min(c.np - 1) * l));
let (mut pend, mut carry, mut o) = ([0u128; 8], 0u128, 0);
for j in (0..n).step_by(8) {
group::<W, ND, NL>(r, rn, o, &ptrs, j, n, c, &mut pend, &mut carry);
o += W / 8;
}
let mut e = 0;
while o + e < rn {
let s = if e < 8 { pend[e] } else { 0 } + carry;
*r.add(o + e) = s as u64;
carry = s >> 64;
e += 1;
}
let mut above = [0; 2];
for a in &mut above {
let s = if e < 8 { pend[e] } else { 0 } + carry;
*a = s as u64;
carry = s >> 64;
e += 1;
}
debug_assert!(carry == 0 && (e..8).all(|e| pend[e] == 0));
above
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct Config {
np: usize,
w: usize,
dn: usize,
b: usize,
crt_cycles: usize,
}
const CONFIGS: [Config; 6] = [
Config {
np: 4,
w: 48,
dn: 2,
b: 24,
crt_cycles: 70,
},
Config {
np: 5,
w: 56,
dn: 2,
b: 28,
crt_cycles: 100,
},
Config {
np: 5,
w: 64,
dn: 3,
b: 22,
crt_cycles: 98,
},
Config {
np: 6,
w: 72,
dn: 3,
b: 24,
crt_cycles: 122,
},
Config {
np: 7,
w: 88,
dn: 4,
b: 22,
crt_cycles: 203,
},
Config {
np: 8,
w: 104,
dn: 4,
b: 26,
crt_cycles: 228,
},
];
#[derive(Clone, Copy)]
struct Plan {
cf: Config,
k: u32,
cx: usize,
cy: usize,
}
fn exact(cf: Config, m: usize) -> bool {
let e = CRT[cf.np].bits as usize - 2 * cf.w - 2;
e >= usize::BITS as usize || m <= 1 << e
}
fn plan(nx: usize, ny: usize) -> Option<Plan> {
let mut best: Option<(Plan, u64)> = None;
if nx > 1 << 22 {
return None;
}
for cf in CONFIGS {
let cx = (64 * nx).div_ceil(cf.w);
let cy = (64 * ny).div_ceil(cf.w);
let (n, shorter) = if ny == 0 {
(2 * cx - 1, cx)
} else {
(cx + cy - 1, cy.min(cx))
};
if !exact(cf, shorter) {
continue;
}
let k = (usize::BITS - (n - 1).leading_zeros()).max(MIN_K);
if k > MAX_K {
continue;
}
let per_point = if ny == 0 {
4 * u64::from(k) + 20
} else {
6 * u64::from(k) + 30
};
let cost = cf.np as u64 * (per_point << k) + n as u64 * cf.crt_cycles as u64;
if best.is_none_or(|(_, c)| cost < c) {
best = Some((Plan { cf, k, cx, cy }, cost));
}
}
best.map(|(p, _)| p)
}
pub(super) fn fits(nx: usize, ny: usize) -> bool {
plan(nx, ny).is_some()
}
pub(super) unsafe fn mul(r: *mut u64, x: *const u64, nx: usize, y: *const u64, ny: usize) {
let p = plan(nx, ny).expect("a product that fits the transforms");
unsafe { product(r, x, nx, y, ny, p) }
}
pub(super) unsafe fn sqr(r: *mut u64, x: *const u64, n: usize) {
let p = plan(n, 0).expect("a square that fits the transforms");
unsafe { product(r, x, n, core::ptr::null(), 0, p) }
}
unsafe fn product(r: *mut u64, x: *const u64, nx: usize, y: *const u64, ny: usize, p: Plan) {
let (cf, k) = (p.cf, p.k);
let (l, np, square) = (1usize << k, cf.np, ny == 0);
let (sx, sy) = (p.cx.next_multiple_of(4), p.cy.next_multiple_of(4));
let n = if square {
2 * p.cx - 1
} else {
p.cx + p.cy - 1
};
let others = if square { 0 } else { l };
let mut buffer = Vec::<i32>::with_capacity((np + 2) * l + others + cf.dn * (sx + sy));
unsafe {
let res = buffer.as_mut_ptr();
let t = res.add(np * l);
let yt = t.add(2 * l);
let dx = yt.add(others);
let dy = dx.add(cf.dn * sx);
digits(dx, sx, x, nx, cf, p.cx);
if !square {
digits(dy, sy, y, ny, cf, p.cy);
}
let crt_data = &CRT[np];
for (i, data) in PRIME_DATA.iter().enumerate().take(np) {
let c = V::consts(&data.prime);
twiddles::<V>(t, k, data);
let r_t: [(i32, i32); 4] = core::array::from_fn(|t| data.pow2[t * cf.b]);
let a = res.add(i * l);
split::<V>(a, k, dx, sx, p.cx, cf.dn, &r_t, t, c);
forward::<V>(a, k, t, c);
if square {
pointwise::<V>(a, a, l, c);
} else {
split::<V>(yt, k, dy, sy, p.cy, cf.dn, &r_t, t, c);
forward::<V>(yt, k, t, c);
pointwise::<V>(a, yt, l, c);
}
inverse::<V>(a, k, t.add(l), c);
let q = data.prime.q as i64;
let mu = centred(
(crt_data.mu[i] as i64 * data.inv2k[k as usize] as i64).rem_euclid(q) as u64,
q as u64,
);
finish::<V>(a, k, n, (mu, barrett(mu, q as u32)), t.add(l), c);
}
let rn = if square { 2 * nx } else { nx + ny };
let above = theorem(r, rn, res, l, n, cf);
debug_assert!(above == [0, 0], "a product fits its limbs");
}
}
unsafe fn theorem(
r: *mut u64,
rn: usize,
res: *const i32,
l: usize,
n: usize,
cf: Config,
) -> [u64; 2] {
let crt_data = &CRT[cf.np];
macro_rules! go {
($w:literal, $nd:literal, $nl:literal) => {
unsafe { crt::<$w, $nd, $nl>(r, rn, res, l, n, crt_data) }
};
}
match (cf.w, crt_data.nd) {
(48, 4) => go!(48, 4, 2),
(56, 5) => go!(56, 5, 3),
(64, 5) => go!(64, 5, 3),
(72, 6) => go!(72, 6, 3),
(88, 7) => go!(88, 7, 4),
(104, 8) => go!(104, 8, 4),
_ => unreachable!("a configuration without its theorem"),
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in super::super) struct Cyclic {
cf: Config,
k: u32,
}
impl Cyclic {
pub(in super::super) fn limbs(self) -> usize {
(self.cf.w << self.k) / 64
}
pub(in super::super) fn cost(self, ready: bool) -> u64 {
let transforms = if ready { 2 } else { 3 };
transforms * self.transform_cost() + (self.cf.crt_cycles << self.k) as u64
}
pub(in super::super) fn transform_cost(self) -> u64 {
let k = u64::from(self.k);
self.cf.np as u64 * ((2 * k + 10) << k)
}
}
pub(in super::super) fn cyclic(min: usize, shorter: usize) -> Option<Cyclic> {
let mut best: Option<(Cyclic, u64)> = None;
if min > 1 << 22 {
return None;
}
for cf in CONFIGS {
let coefficients = (64 * min).div_ceil(cf.w);
let k = (usize::BITS - (coefficients - 1).leading_zeros()).max(MIN_K);
if k > MAX_K {
continue;
}
let m = ((64 * shorter).div_ceil(cf.w)).min(1 << k);
if !exact(cf, m) {
continue;
}
let p = Cyclic { cf, k };
if best.is_none_or(|(_, c)| p.cost(false) < c) {
best = Some((p, p.cost(false)));
}
}
best.map(|(p, _)| p)
}
#[derive(Clone)]
pub(in super::super) struct Twiddles {
k: u32,
np: usize,
t: Vec<i32>,
}
impl Twiddles {
pub(in super::super) fn of(plans: &[Cyclic]) -> Twiddles {
let k = plans.iter().map(|p| p.k).max().unwrap_or(MIN_K);
let np = plans.iter().map(|p| p.cf.np).max().unwrap_or(1);
let l = 1usize << k;
let mut t = Vec::<i32>::with_capacity(2 * l * np);
unsafe {
for (i, data) in PRIME_DATA.iter().enumerate().take(np) {
twiddles::<V>(t.as_mut_ptr().add(2 * l * i), k, data);
}
t.set_len(2 * l * np);
}
Twiddles { k, np, t }
}
fn get(&self, i: usize, p: Cyclic) -> Option<(*const i32, *const i32)> {
if p.k > self.k || i >= self.np {
return None;
}
let base = self.t.as_ptr().wrapping_add((2 * i) << self.k);
Some((base, base.wrapping_add(1 << self.k)))
}
}
unsafe fn twiddles_of(
tw: Option<&Twiddles>,
i: usize,
p: Cyclic,
data: &PrimeData,
t: *mut i32,
) -> (*const i32, *const i32) {
if let Some(pair) = tw.and_then(|tw| tw.get(i, p)) {
return pair;
}
unsafe {
twiddles::<V>(t, p.k, data);
(t, t.add(1 << p.k))
}
}
#[derive(Clone)]
pub(in super::super) struct Transform {
plan: Cyclic,
res: Vec<i32>,
nc: usize,
}
pub(in super::super) unsafe fn transform(
x: *const u64,
nx: usize,
p: Cyclic,
tw: Option<&Twiddles>,
) -> Transform {
let (cf, k) = (p.cf, p.k);
let (l, np) = (1usize << k, cf.np);
let cx = (64 * nx).div_ceil(cf.w);
let sx = cx.next_multiple_of(4);
debug_assert!(nx <= p.limbs() && cx <= l);
let mut res = Vec::<i32>::with_capacity(np * l);
let mut work = Vec::<i32>::with_capacity(2 * l + cf.dn * sx);
unsafe {
let (t, dx) = (work.as_mut_ptr(), work.as_mut_ptr().add(2 * l));
digits(dx, sx, x, nx, cf, cx);
for (i, data) in PRIME_DATA.iter().enumerate().take(np) {
let c = V::consts(&data.prime);
let (tf, _) = twiddles_of(tw, i, p, data, t);
let r_t: [(i32, i32); 4] = core::array::from_fn(|t| data.pow2[t * cf.b]);
let a = res.as_mut_ptr().add(i * l);
split::<V>(a, k, dx, sx, cx, cf.dn, &r_t, tf, c);
forward::<V>(a, k, tf, c);
}
res.set_len(np * l);
}
Transform {
plan: p,
res,
nc: cx,
}
}
pub(in super::super) unsafe fn mul_cyclic(
r: *mut u64,
p: Cyclic,
x: *const u64,
nx: usize,
y: &Transform,
tw: Option<&Twiddles>,
) {
let (cf, k) = (p.cf, p.k);
let (l, np, n) = (1usize << k, cf.np, p.limbs());
let cx = (64 * nx).div_ceil(cf.w);
let sx = cx.next_multiple_of(4);
debug_assert!(nx <= n && cx <= l && y.plan == p && exact(cf, cx.min(y.nc)));
let mut buffer = Vec::<i32>::with_capacity((np + 2) * l + cf.dn * sx);
unsafe {
let res = buffer.as_mut_ptr();
let t = res.add(np * l);
let dx = t.add(2 * l);
digits(dx, sx, x, nx, cf, cx);
let crt_data = &CRT[np];
for (i, data) in PRIME_DATA.iter().enumerate().take(np) {
let c = V::consts(&data.prime);
let (tf, ti) = twiddles_of(tw, i, p, data, t);
let r_t: [(i32, i32); 4] = core::array::from_fn(|t| data.pow2[t * cf.b]);
let a = res.add(i * l);
split::<V>(a, k, dx, sx, cx, cf.dn, &r_t, tf, c);
forward::<V>(a, k, tf, c);
pointwise::<V>(a, y.res.as_ptr().add(i * l), l, c);
inverse::<V>(a, k, ti, c);
let q = data.prime.q as i64;
let mu = centred(
(crt_data.mu[i] as i64 * data.inv2k[k as usize] as i64).rem_euclid(q) as u64,
q as u64,
);
finish::<V>(a, k, l, (mu, barrett(mu, q as u32)), ti, c);
}
let [a0, a1] = theorem(r, n, res, l, l, cf);
let (s0, c0) = (*r).overflowing_add(a0);
*r = s0;
let (s1, c1) = (*r.add(1)).overflowing_add(a1);
let (s1, c2) = s1.overflowing_add(u64::from(c0));
*r.add(1) = s1;
let mut carry = u64::from(c1 | c2);
let mut i = 2;
while carry != 0 && i < n {
let (v, o) = (*r.add(i)).overflowing_add(1);
*r.add(i) = v;
carry = u64::from(o);
i += 1;
}
if carry != 0 {
let mut i = 0;
loop {
let (v, o) = (*r.add(i)).overflowing_add(1);
*r.add(i) = v;
if !o {
break;
}
i += 1;
}
}
}
}