#[cfg(feature = "32_bit_limbs")]
use crate::natural::arithmetic::add::add_with_carry_limb;
#[cfg(not(feature = "32_bit_limbs"))]
use crate::natural::arithmetic::add::limbs_slice_add_same_length_in_place_left;
use crate::natural::arithmetic::mul::context::CONTEXT;
use crate::natural::arithmetic::mul::limb::limbs_mul_limb_to_out;
use crate::natural::arithmetic::shr::limbs_slice_shr_in_place;
use crate::natural::arithmetic::sub::limbs_sub_limb_in_place;
use crate::natural::logic::significant_bits::limbs_significant_bits;
use crate::platform::Limb;
use alloc::vec::Vec;
use core::cmp::Ordering::{self, *};
use core::cmp::{max, min};
use core::fmt::Debug;
use malachite_base::fail_on_untested_path;
#[cfg(feature = "32_bit_limbs")]
use malachite_base::num::arithmetic::traits::ShrRound;
use malachite_base::num::arithmetic::traits::{
CeilingLogBase2, DivRound, ModInverse, ModPow, OverflowingAddAssign, OverflowingSubAssign,
Parity, PowerOf2, RoundToMultiple, RoundToMultipleOfPowerOf2, WrappingAddAssign,
WrappingSubAssign, XMulYToZZ, XXAddYYToZZ, XXXAddYYYToZZZ, XXXXAddYYYYToZZZZ,
};
use malachite_base::num::basic::integers::PrimitiveInt;
use malachite_base::num::basic::traits::One;
use malachite_base::num::conversion::traits::{ExactFrom, SplitInHalf};
use malachite_base::num::logic::traits::{LeadingZeros, SignificantBits};
use malachite_base::rounding_modes::RoundingMode::*;
use wide::{f64x4, f64x8, u64x4};
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub(crate) struct ModData {
pub(crate) n: u64,
pub(crate) ninv: u64,
pub(crate) norm: u64,
}
const SD_FFT_CTX_W2TAB_SIZE: usize = 40;
pub(crate) const SD_FFT_CTX_W2TAB_INIT: u64 = 12;
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct SerializedFFTContext {
pub(crate) p: u64,
pub(crate) pinv: u64,
pub(crate) mod_data: ModData,
pub(crate) primitive_root: u64,
pub(crate) w2tab_depth: u64,
pub(crate) w2tab_backing: [u64; 4096],
pub(crate) w2tab_offsets: [usize; SD_FFT_CTX_W2TAB_SIZE],
}
impl SerializedFFTContext {
fn deserialize(self) -> FFTContext {
FFTContext {
p: f64::from_bits(self.p),
pinv: f64::from_bits(self.pinv),
mod_data: self.mod_data,
primitive_root: self.primitive_root,
w2tab_depth: self.w2tab_depth,
w2tab_backing: self.w2tab_backing.into_iter().map(f64::from_bits).collect(),
w2tab_offsets: self.w2tab_offsets,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct FFTContext {
pub(crate) p: f64,
pub(crate) pinv: f64,
pub(crate) mod_data: ModData,
pub(crate) primitive_root: u64,
pub(crate) w2tab_depth: u64,
pub(crate) w2tab_backing: Vec<f64>,
pub(crate) w2tab_offsets: [usize; SD_FFT_CTX_W2TAB_SIZE],
}
impl Default for FFTContext {
fn default() -> Self {
Self {
p: 0.0,
pinv: 0.0,
mod_data: ModData::default(),
primitive_root: 0,
w2tab_depth: 0,
w2tab_backing: Vec::new(),
w2tab_offsets: [0; SD_FFT_CTX_W2TAB_SIZE],
}
}
}
macro_rules! w2tab {
($q: expr, $i: expr, $j: expr) => {
$q.w2tab_backing[$q.w2tab_offsets[$i] + $j]
};
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct SerializedCRTData {
pub(crate) prime: u64,
pub(crate) coeff_len: usize,
pub(crate) nprimes: usize,
}
impl SerializedCRTData {
fn deserialize(self, data: &[u64]) -> CRTData {
CRTData {
prime: self.prime,
coeff_len: self.coeff_len,
nprimes: self.nprimes,
data: data.to_vec(),
}
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub(crate) struct CRTData {
pub(crate) prime: u64,
pub(crate) coeff_len: usize,
pub(crate) nprimes: usize,
pub(crate) data: Vec<u64>,
}
impl CRTData {
#[inline]
fn co_prime_red(&self, i: usize) -> u64 {
assert!(i < self.nprimes);
self.data[self.nprimes * self.coeff_len + self.coeff_len + i]
}
#[inline]
pub(crate) fn co_prime(&mut self, i: usize) -> &mut [u64] {
assert!(i < self.nprimes);
&mut self.data[i * self.coeff_len..]
}
#[inline]
fn prod_primes_ref(&self) -> &[u64] {
&self.data[self.nprimes * self.coeff_len..]
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub(crate) struct ProfileEntry {
pub(crate) np: usize,
pub(crate) bits: u64,
pub(crate) bn_bound: usize,
pub(crate) to_ffts: Option<MPNToFFTFunc>,
}
pub(crate) const MPN_CTX_NCRTS: usize = 8;
pub(crate) const VEC_SZ: usize = 4;
pub(crate) const MAX_NPROFILES: usize = 20;
pub_test_struct! {
#[derive(Debug, Eq, PartialEq)]
SerializedContext {
pub(crate) ffts: [SerializedFFTContext; MPN_CTX_NCRTS],
pub(crate) crts: [SerializedCRTData; MPN_CTX_NCRTS],
pub(crate) crts_data_0: [u64; 3],
pub(crate) crts_data_1: [u64; 8],
pub(crate) crts_data_2: [u64; 15],
pub(crate) crts_data_3: [u64; 24],
pub(crate) crts_data_4: [u64; 29],
pub(crate) crts_data_5: [u64; 41],
pub(crate) crts_data_6: [u64; 55],
pub(crate) crts_data_7: [u64; 71],
pub(crate) vec_two_pow_tab_backing: [[u64; 4]; 768],
pub(crate) vec_two_pow_tab_offsets: [usize; MPN_CTX_NCRTS.div_ceil(VEC_SZ)],
pub(crate) slow_two_pow_backing: [u64; 1 << 11],
pub(crate) slow_two_pow_offsets: [usize; MPN_CTX_NCRTS],
pub(crate) profiles: [ProfileEntry; MAX_NPROFILES],
pub(crate) profiles_size: usize,
pub(crate) buffer_alloc: usize,
}}
impl SerializedContext {
pub_test! {deserialize(self) -> Context {
let [f0, f1, f2, f3, f4, f5, f6, f7] = self.ffts;
let [c0, c1, c2, c3, c4, c5, c6, c7] = self.crts;
Context {
ffts: [
f0.deserialize(),
f1.deserialize(),
f2.deserialize(),
f3.deserialize(),
f4.deserialize(),
f5.deserialize(),
f6.deserialize(),
f7.deserialize(),
],
crts: [
c0.deserialize(&self.crts_data_0),
c1.deserialize(&self.crts_data_1),
c2.deserialize(&self.crts_data_2),
c3.deserialize(&self.crts_data_3),
c4.deserialize(&self.crts_data_4),
c5.deserialize(&self.crts_data_5),
c6.deserialize(&self.crts_data_6),
c7.deserialize(&self.crts_data_7),
],
vec_two_pow_tab_backing: self
.vec_two_pow_tab_backing
.into_iter()
.map(|[u0, u1, u2, u3]| {
f64x4::from([
f64::from_bits(u0),
f64::from_bits(u1),
f64::from_bits(u2),
f64::from_bits(u3),
])
})
.collect(),
vec_two_pow_tab_offsets: self.vec_two_pow_tab_offsets,
slow_two_pow_backing: self
.slow_two_pow_backing
.into_iter()
.map(f64::from_bits)
.collect(),
slow_two_pow_offsets: self.slow_two_pow_offsets,
profiles: self.profiles,
profiles_size: self.profiles_size,
buffer: Vec::new(),
buffer_alloc: self.buffer_alloc,
}
}}
}
pub_test_struct! {
#[derive(Debug, Default, Clone, PartialEq)]
Context {
pub(crate) ffts: [FFTContext; MPN_CTX_NCRTS],
pub(crate) crts: [CRTData; MPN_CTX_NCRTS],
pub(crate) vec_two_pow_tab_backing: Vec<f64x4>,
pub(crate) vec_two_pow_tab_offsets: [usize; MPN_CTX_NCRTS.div_ceil(VEC_SZ)],
pub(crate) slow_two_pow_backing: Vec<f64>,
pub(crate) slow_two_pow_offsets: [usize; MPN_CTX_NCRTS],
pub(crate) profiles: [ProfileEntry; MAX_NPROFILES],
pub(crate) profiles_size: usize,
pub(crate) buffer: Vec<f64>,
pub(crate) buffer_alloc: usize,
}}
macro_rules! f64_reduce_pm1n_to_pmhn {
($a: expr, $n: expr) => {{
let a = $a;
let n = $n;
let halfn = 0.5 * n;
if a > halfn {
a - n
} else {
let t = a + n;
if t < halfn { t } else { a }
}
}};
}
#[cfg(feature = "test_build")]
pub(crate) use f64_reduce_pm1n_to_pmhn;
macro_rules! f64x4_reduce_pm1n_to_pmhn {
($a: expr, $n: expr) => {{
let [a0, a1, a2, a3] = $a.to_array();
let [n0, n1, n2, n3] = $n.to_array();
f64x4::from([
f64_reduce_pm1n_to_pmhn!(a0, n0),
f64_reduce_pm1n_to_pmhn!(a1, n1),
f64_reduce_pm1n_to_pmhn!(a2, n2),
f64_reduce_pm1n_to_pmhn!(a3, n3),
])
}};
}
macro_rules! f64x8_reduce_pm1n_to_pmhn {
($a: expr, $n: expr) => {{
let [a0, a1, a2, a3, a4, a5, a6, a7] = $a.to_array();
let [n0, n1, n2, n3, n4, n5, n6, n7] = $n.to_array();
f64x8::from([
f64_reduce_pm1n_to_pmhn!(a0, n0),
f64_reduce_pm1n_to_pmhn!(a1, n1),
f64_reduce_pm1n_to_pmhn!(a2, n2),
f64_reduce_pm1n_to_pmhn!(a3, n3),
f64_reduce_pm1n_to_pmhn!(a4, n4),
f64_reduce_pm1n_to_pmhn!(a5, n5),
f64_reduce_pm1n_to_pmhn!(a6, n6),
f64_reduce_pm1n_to_pmhn!(a7, n7),
])
}};
}
macro_rules! f64_reduce_0n_to_pmhn {
($a: expr, $n: expr) => {{
let a = $a;
let n = $n;
if a > 0.5 * n { a - n } else { a }
}};
}
#[cfg(feature = "test_build")]
pub(crate) use f64_reduce_0n_to_pmhn;
macro_rules! f64_reduce_pm1no_to_0n {
($a: expr, $n: expr) => {{
let a = $a;
if a >= 0.0 { a } else { a + $n }
}};
}
macro_rules! f64x4_reduce_pm1no_to_0n {
($a: expr, $n: expr) => {{
let [a0, a1, a2, a3] = $a.to_array();
let [n0, n1, n2, n3] = $n.to_array();
f64x4::from([
f64_reduce_pm1no_to_0n!(a0, n0),
f64_reduce_pm1no_to_0n!(a1, n1),
f64_reduce_pm1no_to_0n!(a2, n2),
f64_reduce_pm1no_to_0n!(a3, n3),
])
}};
}
macro_rules! f64x4_round {
($x: expr) => {{
let [x0, x1, x2, x3] = $x.to_array();
f64x4::from([round_even!(x0), round_even!(x1), round_even!(x2), round_even!(x3)])
}};
}
macro_rules! f64x8_round {
($x: expr) => {{
let [x0, x1, x2, x3, x4, x5, x6, x7] = $x.to_array();
f64x8::from([
round_even!(x0),
round_even!(x1),
round_even!(x2),
round_even!(x3),
round_even!(x4),
round_even!(x5),
round_even!(x6),
round_even!(x7),
])
}};
}
#[cfg(not(any(
all(
target_feature = "fma",
any(target_arch = "x86", target_arch = "x86_64")
),
all(target_feature = "neon", target_arch = "aarch64")
)))]
macro_rules! f64x4_mul_add {
($a: expr, $b: expr, $c: expr) => {{
let [a0, a1, a2, a3] = $a.to_array();
let [b0, b1, b2, b3] = $b.to_array();
let [c0, c1, c2, c3] = $c.to_array();
f64x4::from([fma!(a0, b0, c0), fma!(a1, b1, c1), fma!(a2, b2, c2), fma!(a3, b3, c3)])
}};
}
#[cfg(any(
all(
target_feature = "fma",
any(target_arch = "x86", target_arch = "x86_64")
),
all(target_feature = "neon", target_arch = "aarch64")
))]
macro_rules! f64x4_mul_add {
($a: expr, $b: expr, $c: expr) => {{ $a.mul_add($b, $c) }};
}
#[cfg(feature = "test_build")]
pub(crate) use f64x4_mul_add;
#[cfg(not(any(
all(
target_feature = "fma",
any(target_arch = "x86", target_arch = "x86_64")
),
all(target_feature = "neon", target_arch = "aarch64")
)))]
macro_rules! f64x8_mul_add {
($a: expr, $b: expr, $c: expr) => {{
let [a0, a1, a2, a3, a4, a5, a6, a7] = $a.to_array();
let [b0, b1, b2, b3, b4, b5, b6, b7] = $b.to_array();
let [c0, c1, c2, c3, c4, c5, c6, c7] = $c.to_array();
f64x8::from([
fma!(a0, b0, c0),
fma!(a1, b1, c1),
fma!(a2, b2, c2),
fma!(a3, b3, c3),
fma!(a4, b4, c4),
fma!(a5, b5, c5),
fma!(a6, b6, c6),
fma!(a7, b7, c7),
])
}};
}
#[cfg(any(
all(
target_feature = "fma",
any(target_arch = "x86", target_arch = "x86_64")
),
all(target_feature = "neon", target_arch = "aarch64")
))]
macro_rules! f64x8_mul_add {
($a: expr, $b: expr, $c: expr) => {{ $a.mul_add($b, $c) }};
}
macro_rules! f64x4_reduce_to_pm1no {
($a: expr, $n: expr, $ninv: expr) => {{
let a = $a;
f64x4_mul_add!(-f64x4_round!(a * $ninv), $n, a)
}};
}
macro_rules! f64_reduce_to_pm1no {
($a: expr, $n: expr, $ninv: expr) => {{
let a = $a;
fma!(-round_even!(a * $ninv), $n, a)
}};
}
macro_rules! f64x4_reduce_to_0n {
($a: expr, $n: expr, $ninv: expr) => {{
let n = $n;
f64x4_reduce_pm1no_to_0n!(f64x4_reduce_to_pm1no!($a, n, $ninv), n)
}};
}
macro_rules! f64_reduce_to_0n {
($a: expr, $n: expr, $ninv: expr) => {{
let n = $n;
f64_reduce_pm1no_to_0n!(f64_reduce_to_pm1no!($a, n, $ninv), n)
}};
}
macro_rules! f64_mulmod {
($a: expr, $b: expr, $n: expr, $ninv: expr) => {{
let a = $a;
let b = $b;
let h = a * b;
fma!(-round_even!(h * $ninv), $n, h) - fma!(-a, b, h)
}};
}
#[cfg(feature = "test_build")]
pub(crate) use f64_mulmod;
macro_rules! f64x4_mulmod {
($a: expr, $b: expr, $n: expr, $ninv: expr) => {{
let a = $a;
let b = $b;
let h = a * b;
f64x4_mul_add!(-f64x4_round!(h * $ninv), $n, h) - f64x4_mul_add!(-a, b, h)
}};
}
macro_rules! f64x8_mulmod {
($a: expr, $b: expr, $n: expr, $ninv: expr) => {{
let a = $a;
let b = $b;
let h = a * b;
f64x8_mul_add!(-f64x8_round!(h * $ninv), $n, h) - f64x8_mul_add!(-a, b, h)
}};
}
macro_rules! f64x4_nmulmod {
($a: expr, $b: expr, $n: expr, $ninv: expr) => {{
let a = $a;
let b = $b;
let h = a * b;
f64x4_mul_add!(-a, b, h) - f64x4_mul_add!(-f64x4_round!(h * $ninv), $n, h)
}};
}
macro_rules! f64x8_nmulmod {
($a: expr, $b: expr, $n: expr, $ninv: expr) => {{
let a = $a;
let b = $b;
let h = a * b;
f64x8_mul_add!(-a, b, h) - f64x8_mul_add!(-f64x8_round!(h * $ninv), $n, h)
}};
}
macro_rules! f64_reduce_to_pm1n {
($a: expr, $n: expr, $ninv: expr) => {{
let a = $a;
fma!(-round_even!(a * $ninv), $n, a)
}};
}
macro_rules! f64x4_reduce_to_pm1n {
($a: expr, $n: expr, $ninv: expr) => {{
let a = $a;
f64x4_mul_add!(-f64x4_round!(a * $ninv), $n, a)
}};
}
macro_rules! f64x8_reduce_to_pm1n {
($a: expr, $n: expr, $ninv: expr) => {{
let a = $a;
f64x8_mul_add!(-f64x8_round!(a * $ninv), $n, a)
}};
}
pub(crate) fn crt_data_find_bn_bound(c: &CRTData, bits: u64) -> usize {
let two_bits = bits << 1;
let q = usize::exact_from(two_bits >> u64::LOG_WIDTH);
let r = two_bits & u64::WIDTH_MASK;
let n = c.coeff_len;
let n_p1 = n + 1;
let mut xs = [0; 8];
let xs = &mut xs[..n_p1];
xs[n] = limbs_mul_limb_to_out::<u128, u64>(xs, &c.prod_primes_ref()[..n], bits);
let mut bound = 0;
if q < n_p1 {
let xs_hi = &mut xs[q..];
if r != 0 {
limbs_slice_shr_in_place::<u64>(xs_hi, r);
}
if !limbs_sub_limb_in_place::<u64>(xs_hi, bits - 1) {
limbs_slice_shr_in_place::<u64>(xs_hi, 6);
bound = usize::exact_from(xs_hi[0]);
if xs_hi[1..].iter().any(|&x| x != 0) {
return usize::ONE.wrapping_neg();
}
}
}
bound
}
fn crt_data_find_bits(c: &CRTData, bn: usize) -> u64 {
let p_nbits = limbs_significant_bits(&c.prod_primes_ref()[..c.coeff_len]);
let mut bits = max(66, (p_nbits - bn.significant_bits()) >> 1);
if bn > crt_data_find_bn_bound(c, bits) {
bits -= 1;
while bits > 65 && bn > crt_data_find_bn_bound(c, bits) {
bits -= 1;
}
} else {
while bn <= crt_data_find_bn_bound(c, bits + 1) {
bits += 1;
}
}
bits
}
#[cfg(feature = "32_bit_limbs")]
macro_rules! get {
($a: ident, $i: expr) => {
$a[$i]
};
}
#[cfg(not(feature = "32_bit_limbs"))]
macro_rules! get {
($a: ident, $i: expr) => {{
let i = $i;
let x = $a[i >> 1];
if i & 1 == 0 {
x.lower_half()
} else {
x.upper_half()
}
}};
}
macro_rules! code {
(
$ir: expr,
$nvs: expr,
$np: expr,
$ds: ident,
$dstride: ident,
$xs: ident,
$ps: ident,
$pinvs: ident,
$two_pow: ident,
$a: ident,
$i: ident,
$bits: expr
) => {
let mut k = (($i + $ir) * $bits) / 32;
let mut j = const { ($ir * $bits) % 32 };
$xs[..$nvs].fill(f64x4::splat(f64::from(get!($a, k) >> j)));
k += 1;
j = const { 32 - ($ir * $bits) % 32 };
let mut m = const { (32 - ($ir * $bits) % 32) * $nvs };
while j <= const { $bits - 32 } {
let ak = f64x4::splat(f64::from(get!($a, k)));
let two_pow_hi = &$two_pow[m..];
for l in 0..$nvs {
$xs[l] += f64x4_mulmod!(ak, two_pow_hi[l], $ps[l], $pinvs[l]);
}
k += 1;
j += 32;
m += const { $nvs << 5 };
}
let bmj = $bits - j;
if bmj != 0 {
let ak = f64x4::splat(f64::from(get!($a, k) << (32 - bmj)));
let two_pow_hi = &$two_pow[const { ($bits - 32) * $nvs }..];
for l in 0..$nvs {
$xs[l] += f64x4_mulmod!(ak, two_pow_hi[l], $ps[l], $pinvs[l]);
}
}
for l in 0..$nvs {
$xs[l] = f64x4_reduce_to_pm1n!($xs[l], $ps[l], $pinvs[l]);
}
let ds_hi = &mut $ds[$i + $ir..];
let mut m = 0;
for l in 0..$np {
ds_hi[m] = $xs[l >> 2].to_array()[l & 3];
m += $dstride;
}
};
}
#[cfg(feature = "32_bit_limbs")]
macro_rules! get_or_default {
($a: ident, $i: expr) => {
$a.get($i).copied().unwrap_or_default()
};
}
#[cfg(not(feature = "32_bit_limbs"))]
macro_rules! get_or_default {
($a: ident, $i: expr) => {{
let i = $i;
let j = i >> 1;
if j < $a.len() {
let x = $a[j];
if i & 1 == 0 {
x.lower_half()
} else {
x.upper_half()
}
} else {
0
}
}};
}
macro_rules! mpn_to_ffts_hard {
(
$np: expr,
$nvs: expr,
$rffts: ident,
$ds: ident,
$dstride: ident,
$a: ident,
$atrunc: ident,
$two_pow: ident,
$start_hard: ident,
$stop_hard: ident,
$bits: expr
) => {{
let mut xs = [f64x4::default(); $nvs];
let mut ps = [f64x4::default(); $nvs];
let mut pinvs = [f64x4::default(); $nvs];
for (i, r) in $rffts.chunks(4).enumerate() {
ps[i] = f64x4::new([r[0].p, r[1].p, r[2].p, r[3].p]);
pinvs[i] = f64x4::new([r[0].pinv, r[1].pinv, r[2].pinv, r[3].pinv]);
}
for i in $start_hard..$stop_hard {
let ib = i * $bits;
let mut k = ib >> 5;
let mut j = ib & 31;
xs[..$nvs].fill(f64x4::splat(f64::from(get_or_default!($a, k) >> j)));
k += 1;
j = 32 - j;
let bm32 = $bits - 32;
while j <= bm32 {
let ak = f64x4::splat(f64::from(get_or_default!($a, k)));
let two_pow_hi = &$two_pow[j * $nvs..];
for l in 0..$nvs {
xs[l] += f64x4_mulmod!(ak, two_pow_hi[l], ps[l], pinvs[l]);
}
k += 1;
j += 32;
}
let bmj = $bits - j;
if bmj != 0 {
let ak = f64x4::splat(f64::from(get_or_default!($a, k) << (32 - bmj)));
let two_pow_hi = &$two_pow[bm32 * $nvs..];
for l in 0..$nvs {
xs[l] += f64x4_mulmod!(ak, two_pow_hi[l], ps[l], pinvs[l]);
}
}
for l in 0..$nvs {
xs[l] = f64x4_reduce_to_pm1n!(xs[l], ps[l], pinvs[l]);
}
let ds_hi = &mut $ds[i..];
let mut m = 0;
for l in 0..$np {
ds_hi[m] = xs[l / VEC_SZ].to_array()[l % VEC_SZ];
m += $dstride;
}
}
for l in 0..$np {
$ds[l * $dstride..][$stop_hard..$atrunc].fill(0.0);
}
}};
}
pub(crate) const MPN_CTX_TWO_POWER_TAB_SIZE: usize = 256;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(crate) struct MPNToFFTFunc {
pub(crate) np: usize,
pub(crate) bits: u64,
}
macro_rules! apply_mpn_to_fft_func {
($f: ident, $np: expr, $bits: expr, $nvs: expr) => {
fn $f(
rffts: &[FFTContext],
ds: &mut [f64],
dstride: usize,
a: &[Limb],
atrunc: usize,
two_pow: &[f64x4],
stop_easy: usize,
start_hard: usize,
stop_hard: usize,
) {
let mut xs = [f64x4::default(); $nvs];
let mut ps = [f64x4::default(); $nvs];
let mut pinvs = [f64x4::default(); $nvs];
for (i, r) in rffts.chunks(4).enumerate() {
ps[i] = f64x4::from([r[0].p, r[1].p, r[2].p, r[3].p]);
pinvs[i] = f64x4::from([r[0].pinv, r[1].pinv, r[2].pinv, r[3].pinv]);
}
if const { $bits & 7 == 0 } {
assert_eq!(stop_easy & 3, 0);
for i in (0..stop_easy).step_by(4) {
code!(
0, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
1, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
2, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
3, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
}
} else {
assert_eq!(stop_easy & 7, 0);
for i in (0..stop_easy).step_by(8) {
code!(
0, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
1, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
2, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
3, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
4, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
5, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
6, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
code!(
7, $nvs, $np, ds, dstride, xs, ps, pinvs, two_pow, a, i, $bits
);
}
}
mpn_to_ffts_hard!(
$np, $nvs, rffts, ds, dstride, a, atrunc, two_pow, start_hard, stop_hard, $bits
);
}
};
}
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_4_84, 4, 84, 1);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_4_88, 4, 88, 1);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_4_92, 4, 92, 1);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_5_112, 5, 112, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_5_116, 5, 116, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_5_120, 5, 120, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_6_136, 6, 136, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_6_140, 6, 140, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_6_144, 6, 144, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_7_160, 7, 160, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_7_164, 7, 164, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_7_168, 7, 168, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_8_184, 8, 184, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_8_188, 8, 188, 2);
apply_mpn_to_fft_func!(apply_mpn_to_fft_func_8_192, 8, 192, 2);
const LG_BLK_SZ: u64 = 8;
const BLK_SZ: usize = 256;
fn mpn_ctx_best_profile(r: &Context, p: &mut ProfileEntry, an: usize, bn: usize) {
const BIGGEST_BOUND: usize = CONTEXT.profiles[0].bn_bound;
const PROFILES_SIZE: usize = CONTEXT.profiles_size;
if bn > BIGGEST_BOUND {
p.np = 4;
p.bits = crt_data_find_bits(&r.crts[p.np - 1], bn);
p.to_ffts = None;
return;
}
let mut i = 0;
let mut best_i = 0;
let mut best_score = 100000000.0 * (an.checked_add(bn).unwrap() as f64);
loop {
assert!(i < PROFILES_SIZE);
assert!(bn <= r.profiles[i].bn_bound);
while i + 1 < PROFILES_SIZE
&& bn <= r.profiles[i + 1].bn_bound
&& r.profiles[i + 1].np == r.profiles[i].np
{
i += 1;
}
let np = r.profiles[i].np;
let bits = r.profiles[i].bits as usize;
let alen = (an << 6).div_round(bits, Ceiling).0;
let blen = (bn << 6).div_round(bits, Ceiling).0;
let zlen = alen + blen - 1;
let ztrunc = zlen.round_to_multiple(BLK_SZ, Ceiling).0;
let depth = max(LG_BLK_SZ, ztrunc.ceiling_log_base_2());
let ratio = (ztrunc as f64) / (f64::power_of_2(depth));
let mut score = (1.0 - 0.25 * ratio) * const { 1.0 / 1000000.0 };
score *= (np * usize::exact_from(depth)) as f64;
score *= ztrunc as f64;
if score < best_score {
best_i = i;
best_score = score;
}
loop {
i += 1;
if i >= PROFILES_SIZE {
p.np = r.profiles[best_i].np;
p.bits = r.profiles[best_i].bits;
p.to_ffts = r.profiles[best_i].to_ffts;
return;
}
if bn <= r.profiles[i].bn_bound {
break;
}
}
}
}
fn mpn_ctx_fit_buffer(r: &mut Context, n: usize) {
if n > r.buffer_alloc {
let n = max(n, (r.buffer_alloc * 17) >> 4)
.round_to_multiple_of_power_of_2(12, Ceiling)
.0;
r.buffer.resize(n >> 3, 0.0);
r.buffer_alloc = n;
}
}
fn slow_mpn_to_fft_easy(
q: &FFTContext,
zs: &mut [f64],
a: &[Limb],
iq_stop_easy: usize,
bits: usize,
two_pow: &[f64],
) {
let p = f64x8::splat(q.p);
let pinv = f64x8::splat(q.pinv);
let b2 = bits << 1;
let b3 = b2 + bits;
let b4 = b3 + bits;
let b5 = b4 + bits;
let b6 = b5 + bits;
let b7 = b6 + bits;
for iq in 0..iq_stop_easy {
let zi = &mut zs[iq << LG_BLK_SZ..];
let mut m = 0;
for ir in 0..const { BLK_SZ >> 3 } {
let mut k = iq * const { BLK_SZ >> 5 } * bits + (m >> 5);
let mut j = m & 31;
let mut ak = f64x8::from([
f64::from(get!(a, k) >> j),
f64::from(get!(a, k + bits) >> j),
f64::from(get!(a, k + b2) >> j),
f64::from(get!(a, k + b3) >> j),
f64::from(get!(a, k + b4) >> j),
f64::from(get!(a, k + b5) >> j),
f64::from(get!(a, k + b6) >> j),
f64::from(get!(a, k + b7) >> j),
]);
let mut x = ak;
k += 1;
j = 32 - j;
let bm32 = bits - 32;
while j <= bm32 {
ak = f64x8::from([
f64::from(get!(a, k)),
f64::from(get!(a, k + bits)),
f64::from(get!(a, k + b2)),
f64::from(get!(a, k + b3)),
f64::from(get!(a, k + b4)),
f64::from(get!(a, k + b5)),
f64::from(get!(a, k + b6)),
f64::from(get!(a, k + b7)),
]);
x += f64x8_mulmod!(ak, f64x8::splat(two_pow[j]), p, pinv);
k += 1;
j += 32;
}
let bmj = bits - j;
if bmj != 0 {
let shift = 32 - bmj;
ak = f64x8::from([
f64::from(get!(a, k) << shift),
f64::from(get!(a, k + bits) << shift),
f64::from(get!(a, k + b2) << shift),
f64::from(get!(a, k + b3) << shift),
f64::from(get!(a, k + b4) << shift),
f64::from(get!(a, k + b5) << shift),
f64::from(get!(a, k + b6) << shift),
f64::from(get!(a, k + b7) << shift),
]);
x += f64x8_mulmod!(ak, f64x8::splat(two_pow[bm32]), p, pinv);
}
x = f64x8_reduce_to_pm1n!(x, p, pinv);
let [x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7] = x.to_array();
let zi_hi = &mut zi[ir..];
const B: usize = BLK_SZ >> 3;
zi_hi[0] = x_0;
zi_hi[B] = x_1;
zi_hi[const { 2 * B }] = x_2;
zi_hi[const { 3 * B }] = x_3;
zi_hi[const { 4 * B }] = x_4;
zi_hi[const { 5 * B }] = x_5;
zi_hi[const { 6 * B }] = x_6;
zi_hi[const { 7 * B }] = x_7;
m += bits;
}
}
}
fn slow_mpn_to_fft(
q: &FFTContext,
zs: &mut [f64],
ztrunc: usize,
a: &[Limb],
bits: usize,
two_pow: &[f64],
) {
let an = a.len();
#[cfg(not(feature = "32_bit_limbs"))]
let an = an << 1;
let bm32 = bits - 32;
assert!(bm32 < MPN_CTX_TWO_POWER_TAB_SIZE);
let i_stop_easy = min(ztrunc, ((an << 5) - 33) / bits);
let iq_stop_easy = i_stop_easy >> LG_BLK_SZ;
slow_mpn_to_fft_easy(q, zs, a, iq_stop_easy, bits, two_pow);
let p = q.p;
let pinv = q.pinv;
for iq in iq_stop_easy..ztrunc >> LG_BLK_SZ {
let big_i = iq << LG_BLK_SZ;
for (i, z) in zs[big_i..].iter_mut().enumerate().take(BLK_SZ) {
let n = (big_i + i) * bits;
let mut k = n >> 5;
let mut j = n & 31;
let mut x = f64::from(get_or_default!(a, k) >> j);
k += 1;
j = 32 - j;
while j <= bm32 {
x += f64_mulmod!(f64::from(get_or_default!(a, k)), two_pow[j], p, pinv);
k += 1;
j += 32;
}
let bmj = bits - j;
if bmj != 0 {
x += f64_mulmod!(
f64::from(get_or_default!(a, k) << (32 - bmj)),
two_pow[bm32],
p,
pinv
);
}
*z = f64_reduce_to_pm1n!(x, p, pinv);
}
}
}
macro_rules! n_nbits_nz {
($x: expr) => {{ (LeadingZeros::leading_zeros($x) ^ const { usize::WIDTH - 1 }) + 1 }};
}
macro_rules! set_j_bits_and_j_r {
($j: ident, $j_bits: ident, $j_r: ident) => {
let ($j_bits, $j_r) = if $j == 0 {
(0, 0)
} else {
let j_bits = n_nbits_nz!($j);
(j_bits, $j - usize::power_of_2(j_bits - 1))
};
};
}
macro_rules! radix_4_forward_param_j_is_z {
($q: ident, $iw: ident, $n: ident, $ninv: ident) => {
let $iw = f64x8::splat(w2tab!($q, 1, 0));
let $n = f64x8::splat($q.p);
let $ninv = f64x8::splat($q.pinv);
};
}
macro_rules! radix_4_forward_moth_j_is_z {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$iw: ident,
$n: ident,
$ninv: ident
) => {
$x0 = f64x8_reduce_to_pm1n!($x0, $n, $ninv);
$x2 = f64x8_reduce_to_pm1n!($x2, $n, $ninv);
$x3 = f64x8_reduce_to_pm1n!($x3, $n, $ninv);
let y0 = $x0 + $x2;
let y1 = f64x8_reduce_to_pm1n!($x1 + $x3, $n, $ninv);
let y2 = $x0 - $x2;
let y3 = f64x8_mulmod!($x1 - $x3, $iw, $n, $ninv);
$x0 = y0 + y1;
$x1 = y0 - y1;
$x2 = y2 + y3;
$x3 = y2 - y3;
};
}
macro_rules! radix_4_forward_param_j_is_nz {
(
$q: ident,
$j_r: ident,
$j_bits: ident,
$w: ident,
$w2: ident,
$iw: ident,
$n: ident,
$ninv: ident
) => {
assert_ne!($j_bits, 0);
let j_bits = usize::exact_from($j_bits);
let j_bits_p_1 = j_bits + 1;
let j_2 = $j_r << 1;
let $w = f64x8::splat(w2tab!($q, j_bits_p_1, j_2));
let $w2 = f64x8::splat(w2tab!($q, j_bits, $j_r));
let $iw = f64x8::splat(w2tab!($q, j_bits_p_1, j_2 + 1));
let $n = f64x8::splat($q.p);
let $ninv = f64x8::splat($q.pinv);
};
}
macro_rules! radix_4_forward_moth_j_is_nz {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$w: ident,
$w2: ident,
$iw: ident,
$n: ident,
$ninv: ident
) => {
$x0 = f64x8_reduce_to_pm1n!($x0, $n, $ninv);
$x2 = f64x8_mulmod!($x2, $w2, $n, $ninv);
$x3 = f64x8_mulmod!($x3, $w2, $n, $ninv);
let y0 = $x0 + $x2;
let y1 = f64x8_mulmod!($x1 + $x3, $w, $n, $ninv);
let y2 = $x0 - $x2;
let y3 = f64x8_mulmod!($x1 - $x3, $iw, $n, $ninv);
$x0 = y0 + y1;
$x1 = y0 - y1;
$x2 = y2 + y3;
$x3 = y2 - y3;
};
}
macro_rules! radix_2_forward_param_j_is_z {
($q: ident, $n: ident, $ninv: ident) => {
let $n = f64x8::splat($q.p);
let $ninv = f64x8::splat($q.pinv);
};
}
macro_rules! radix_2_forward_moth_j_is_z {
($x0: ident, $x1: ident, $n: ident, $ninv: ident) => {
$x0 = f64x8_reduce_to_pm1n!($x0, $n, $ninv);
$x1 = f64x8_reduce_to_pm1n!($x1, $n, $ninv);
($x0, $x1) = ($x0 + $x1, $x0 - $x1);
};
}
macro_rules! radix_2_forward_param_j_is_nz {
($q: ident, $j_r: ident, $j_bits: ident, $w: ident, $n: ident, $ninv: ident) => {
let $w = f64x8::splat(w2tab!($q, usize::exact_from($j_bits), $j_r));
let $n = f64x8::splat($q.p);
let $ninv = f64x8::splat($q.pinv);
};
}
macro_rules! radix_2_forward_moth_j_is_nz {
($x0: ident, $x1: ident, $w: ident, $n: ident, $ninv: ident) => {
$x0 = f64x8_reduce_to_pm1n!($x0, $n, $ninv);
$x1 = f64x8_mulmod!($x1, $w, $n, $ninv);
($x0, $x1) = ($x0 + $x1, $x0 - $x1);
};
}
const BIG_N: usize = 8;
macro_rules! read_f64x4 {
($xs: expr, $i: expr) => {{
let i = $i;
f64x4::from(&$xs[i..i + 4])
}};
}
macro_rules! read_f64x8 {
($xs: expr, $i: expr) => {{
let i = $i;
f64x8::from(&$xs[i..i + 8])
}};
}
macro_rules! read_f64x4_w2tab {
($q: expr, $i: expr, $j: expr) => {
read_f64x4!($q.w2tab_backing, $q.w2tab_offsets[$i] + $j)
};
}
macro_rules! read_f64x8_w2tab {
($q: expr, $i: expr, $j: expr) => {
read_f64x8!($q.w2tab_backing, $q.w2tab_offsets[$i] + $j)
};
}
macro_rules! write_f64x4 {
($xs: ident, $i: expr, $f: expr) => {{
let i = $i;
$xs[i..i + 4].copy_from_slice(&$f.to_array())
}};
}
macro_rules! write_f64x8 {
($xs: ident, $i: expr, $f: expr) => {{
let i = $i;
$xs[i..i + 8].copy_from_slice(&$f.to_array())
}};
}
macro_rules! write_f64x8_w2tab {
($q: expr, $i: expr, $j: expr, $f: expr) => {
let start = $q.w2tab_offsets[$i] + $j;
$q.w2tab_backing[start..start + 8].copy_from_slice(&$f.to_array());
};
}
macro_rules! process_2_2 {
($f: ident, $limit: expr, $x0: ident, $x1: ident, $p0: ident, $p1: ident) => {
for i in (0..$limit).step_by(BIG_N) {
let mut f0 = read_f64x8!($x0, i);
let mut f1 = read_f64x8!($x1, i);
$f!(f0, f1, $p0, $p1);
write_f64x8!($x0, i, f0);
write_f64x8!($x1, i, f1);
}
};
}
macro_rules! process_2_3 {
($f: ident, $limit: expr, $x0: ident, $x1: ident, $p0: ident, $p1: ident, $p2: ident) => {
for i in (0..$limit).step_by(BIG_N) {
let mut f0 = read_f64x8!($x0, i);
let mut f1 = read_f64x8!($x1, i);
$f!(f0, f1, $p0, $p1, $p2);
write_f64x8!($x0, i, f0);
write_f64x8!($x1, i, f1);
}
};
}
macro_rules! process_4_3 {
(
$f: ident,
$limit: expr,
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$p0: ident,
$p1: ident,
$p2: ident
) => {
for i in (0..$limit).step_by(BIG_N) {
let mut f0 = read_f64x8!($x0, i);
let mut f1 = read_f64x8!($x1, i);
let mut f2 = read_f64x8!($x2, i);
let mut f3 = read_f64x8!($x3, i);
$f!(f0, f1, f2, f3, $p0, $p1, $p2);
write_f64x8!($x0, i, f0);
write_f64x8!($x1, i, f1);
write_f64x8!($x2, i, f2);
write_f64x8!($x3, i, f3);
}
};
}
macro_rules! process_4_5 {
(
$f: ident,
$limit: expr,
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$p0: ident,
$p1: ident,
$p2: ident,
$p3: ident,
$p4: ident
) => {
for i in (0..$limit).step_by(BIG_N) {
let mut f0 = read_f64x8!($x0, i);
let mut f1 = read_f64x8!($x1, i);
let mut f2 = read_f64x8!($x2, i);
let mut f3 = read_f64x8!($x3, i);
$f!(f0, f1, f2, f3, $p0, $p1, $p2, $p3, $p4);
write_f64x8!($x0, i, f0);
write_f64x8!($x1, i, f1);
write_f64x8!($x2, i, f2);
write_f64x8!($x3, i, f3);
}
};
}
fn sd_fft_no_trunc_block(q: &FFTContext, xs: &mut [f64], s: usize, k: u64, j: usize) {
let big_s = s << LG_BLK_SZ;
if k > 4 {
let k1 = k >> 1;
let k2 = k - k1;
let shifted = s << k2;
let mut m = 0;
for _ in 0..usize::power_of_2(k2) {
sd_fft_no_trunc_block(q, &mut xs[m..], shifted, k1, j);
m += big_s;
}
let big_s = s << (k2 + LG_BLK_SZ);
let shifted = j << k1;
let mut m = 0;
for b in 0..usize::power_of_2(k1) {
sd_fft_no_trunc_block(q, &mut xs[m..], s, k2, shifted + b);
m += big_s;
}
return;
}
set_j_bits_and_j_r!(j, j_bits, j_r);
if k >= 2 {
let k1 = 2;
let k2 = k - k1;
let l2 = usize::power_of_2(k2);
let big_s_2 = s << (k2 + LG_BLK_SZ);
if j_bits == 0 {
radix_4_forward_param_j_is_z!(q, iw, n, ninv);
for a in 0..l2 {
split_into_chunks_mut!(&mut xs[a * big_s..], big_s_2, [x0, x1, x2], x3);
process_4_3!(
radix_4_forward_moth_j_is_z,
BLK_SZ,
x0,
x1,
x2,
x3,
iw,
n,
ninv
);
}
} else {
radix_4_forward_param_j_is_nz!(q, j_r, j_bits, w, w2, iw, n, ninv);
for a in 0..l2 {
split_into_chunks_mut!(&mut xs[a * big_s..], big_s_2, [x0, x1, x2], x3);
process_4_5!(
radix_4_forward_moth_j_is_nz,
BLK_SZ,
x0,
x1,
x2,
x3,
w,
w2,
iw,
n,
ninv
);
}
}
if l2 == 1 {
return;
}
let shifted = j << k1;
let mut m = 0;
for b in 0..usize::power_of_2(k1) {
sd_fft_no_trunc_block(q, &mut xs[m..], s, k2, shifted + b);
m += big_s_2;
}
} else if k == 1 {
let (x0, x1) = xs.split_at_mut(big_s);
if j_bits == 0 {
radix_2_forward_param_j_is_z!(q, n, ninv);
process_2_2!(radix_2_forward_moth_j_is_z, BLK_SZ, x0, x1, n, ninv);
} else {
radix_2_forward_param_j_is_nz!(q, j_r, j_bits, w, n, ninv);
process_2_3!(radix_2_forward_moth_j_is_nz, BLK_SZ, x0, x1, w, n, ninv);
}
}
}
macro_rules! f64_length4_zero_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$n: expr,
$ninv: expr,
$e14: expr
) => {
let n = $n;
let ninv = $ninv;
*$x0 = f64_reduce_to_pm1n!(*$x0, n, ninv);
*$x2 = f64_reduce_to_pm1n!(*$x2, n, ninv);
*$x3 = f64_reduce_to_pm1n!(*$x3, n, ninv);
let y0 = *$x0 + *$x2;
let y1 = f64_reduce_to_pm1n!(*$x1 + *$x3, n, ninv);
let y2 = *$x0 - *$x2;
let y3 = f64_mulmod!(*$x1 - *$x3, $e14, n, ninv);
*$x0 = y0 + y1;
*$x1 = y0 - y1;
*$x2 = y2 + y3;
*$x3 = y2 - y3;
};
}
macro_rules! f64x4_length4_zero_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$n: ident,
$ninv: ident,
$e14: ident
) => {
$x0 = f64x4_reduce_to_pm1n!($x0, $n, $ninv);
$x2 = f64x4_reduce_to_pm1n!($x2, $n, $ninv);
$x3 = f64x4_reduce_to_pm1n!($x3, $n, $ninv);
let y0 = $x0 + $x2;
let y1 = f64x4_reduce_to_pm1n!($x1 + $x3, $n, $ninv);
let y2 = $x0 - $x2;
let y3 = f64x4_mulmod!($x1 - $x3, $e14, $n, $ninv);
$x0 = y0 + y1;
$x1 = y0 - y1;
$x2 = y2 + y3;
$x3 = y2 - y3;
};
}
macro_rules! length4_any_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$n: ident,
$ninv: ident,
$w2: ident,
$w: ident,
$iw: ident
) => {
$x0 = f64x4_reduce_to_pm1n!($x0, $n, $ninv);
$x2 = f64x4_mulmod!($x2, $w2, $n, $ninv);
$x3 = f64x4_mulmod!($x3, $w2, $n, $ninv);
let y0 = $x0 + $x2;
let y1 = f64x4_mulmod!($x1 + $x3, $w, $n, $ninv);
let y2 = $x0 - $x2;
let y3 = f64x4_mulmod!($x1 - $x3, $iw, $n, $ninv);
$x0 = y0 + y1;
$x1 = y0 - y1;
$x2 = y2 + y3;
$x3 = y2 - y3;
};
}
macro_rules! f64_length8_zero_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$x4: ident,
$x5: ident,
$x6: ident,
$x7: ident,
$n: expr,
$ninv: expr,
$e14: expr,
$e18: expr,
$e38: expr
) => {
let n = $n;
let ninv = $ninv;
let e14 = $e14;
let y0 = f64_reduce_to_pm1n!(*$x0 + *$x4, n, ninv);
let y1 = f64_reduce_to_pm1n!(*$x1 + *$x5, n, ninv);
let y2 = f64_reduce_to_pm1n!(*$x2 + *$x6, n, ninv);
let y3 = f64_reduce_to_pm1n!(*$x3 + *$x7, n, ninv);
let y4 = f64_reduce_to_pm1n!(*$x0 - *$x4, n, ninv);
let y5 = f64_reduce_to_pm1n!(*$x1 - *$x5, n, ninv);
let y6 = f64_mulmod!(e14, f64_reduce_to_pm1n!(*$x2 - *$x6, n, ninv), n, ninv);
let y7 = f64_mulmod!(e14, f64_reduce_to_pm1n!(*$x3 - *$x7, n, ninv), n, ninv);
let z0 = y0 + y2;
let z1 = y1 + y3;
let z2 = y0 - y2;
let z3 = f64_mulmod!(e14, y1 - y3, n, ninv);
let z4 = y4 + y6;
let z5 = f64_mulmod!($e18, y5 + y7, n, ninv);
let z6 = y4 - y6;
let z7 = f64_mulmod!($e38, y5 - y7, n, ninv);
*$x0 = z0 + z1;
*$x1 = z0 - z1;
*$x2 = z2 + z3;
*$x3 = z2 - z3;
*$x4 = z4 + z5;
*$x5 = z4 - z5;
*$x6 = z6 + z7;
*$x7 = z6 - z7;
};
}
macro_rules! f64x4_length8_zero_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$x4: ident,
$x5: ident,
$x6: ident,
$x7: ident,
$n: ident,
$ninv: ident,
$e14: ident,
$e18: ident,
$e38: ident
) => {
let y0 = f64x4_reduce_to_pm1n!($x0 + $x4, $n, $ninv);
let y1 = f64x4_reduce_to_pm1n!($x1 + $x5, $n, $ninv);
let y2 = f64x4_reduce_to_pm1n!($x2 + $x6, $n, $ninv);
let y3 = f64x4_reduce_to_pm1n!($x3 + $x7, $n, $ninv);
let y4 = f64x4_reduce_to_pm1n!($x0 - $x4, $n, $ninv);
let y5 = f64x4_reduce_to_pm1n!($x1 - $x5, $n, $ninv);
let y6 = f64x4_mulmod!($e14, f64x4_reduce_to_pm1n!($x2 - $x6, $n, $ninv), $n, $ninv);
let y7 = f64x4_mulmod!($e14, f64x4_reduce_to_pm1n!($x3 - $x7, $n, $ninv), $n, $ninv);
let z0 = y0 + y2;
let z1 = y1 + y3;
let z2 = y0 - y2;
let z3 = f64x4_mulmod!($e14, y1 - y3, $n, $ninv);
let z4 = y4 + y6;
let z5 = f64x4_mulmod!($e18, y5 + y7, $n, $ninv);
let z6 = y4 - y6;
let z7 = f64x4_mulmod!($e38, y5 - y7, $n, $ninv);
$x0 = z0 + z1;
$x1 = z0 - z1;
$x2 = z2 + z3;
$x3 = z2 - z3;
$x4 = z4 + z5;
$x5 = z4 - z5;
$x6 = z6 + z7;
$x7 = z6 - z7;
};
}
macro_rules! length8_any_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$x4: ident,
$x5: ident,
$x6: ident,
$x7: ident,
$n: ident,
$ninv: ident,
$w2: ident,
$w: ident,
$iw: ident,
$ww0: ident,
$ww1: ident,
$ww2: ident,
$ww3: ident
) => {
$x0 = f64x4_reduce_to_pm1n!($x0, $n, $ninv);
$x1 = f64x4_reduce_to_pm1n!($x1, $n, $ninv);
$x4 = f64x4_mulmod!($x4, $w2, $n, $ninv);
$x5 = f64x4_mulmod!($x5, $w2, $n, $ninv);
$x6 = f64x4_mulmod!($x6, $w2, $n, $ninv);
$x7 = f64x4_mulmod!($x7, $w2, $n, $ninv);
let y0 = $x0 + $x4;
let y1 = $x1 + $x5;
let y2 = f64x4_mulmod!($x2 + $x6, $w, $n, $ninv);
let y3 = f64x4_mulmod!($x3 + $x7, $w, $n, $ninv);
let y4 = $x0 - $x4;
let y5 = $x1 - $x5;
let y6 = f64x4_mulmod!($x2 - $x6, $iw, $n, $ninv);
let y7 = f64x4_mulmod!($x3 - $x7, $iw, $n, $ninv);
let z0 = f64x4_reduce_to_pm1n!(y0 + y2, $n, $ninv);
let z1 = f64x4_mulmod!(y1 + y3, $ww0, $n, $ninv);
let z2 = f64x4_reduce_to_pm1n!(y0 - y2, $n, $ninv);
let z3 = f64x4_mulmod!(y1 - y3, $ww1, $n, $ninv);
let z4 = f64x4_reduce_to_pm1n!(y4 + y6, $n, $ninv);
let z5 = f64x4_mulmod!(y5 + y7, $ww2, $n, $ninv);
let z6 = f64x4_reduce_to_pm1n!(y4 - y6, $n, $ninv);
let z7 = f64x4_mulmod!(y5 - y7, $ww3, $n, $ninv);
$x0 = z0 + z1;
$x1 = z0 - z1;
$x2 = z2 + z3;
$x3 = z2 - z3;
$x4 = z4 + z5;
$x5 = z4 - z5;
$x6 = z6 + z7;
$x7 = z6 - z7;
};
}
macro_rules! f64x4_unpack_lo_permute_0_2_1_3 {
($u: ident, $v: ident) => {{
let [u0, _, u2, _] = $u.to_array();
let [v0, _, v2, _] = $v.to_array();
f64x4::from([u0, u2, v0, v2])
}};
}
macro_rules! f64x4_unpack_hi_permute_0_2_1_3 {
($u: ident, $v: ident) => {{
let [_, u1, _, u3] = $u.to_array();
let [_, v1, _, v3] = $v.to_array();
f64x4::from([u1, u3, v1, v3])
}};
}
macro_rules! f64x4_transpose {
($a0: ident, $a1: ident, $a2: ident, $a3: ident) => {{
let [a00, a01, a02, a03] = $a0.to_array();
let [a10, a11, a12, a13] = $a1.to_array();
let [a20, a21, a22, a23] = $a2.to_array();
let [a30, a31, a32, a33] = $a3.to_array();
$a0 = f64x4::from([a00, a10, a20, a30]);
$a1 = f64x4::from([a01, a11, a21, a31]);
$a2 = f64x4::from([a02, a12, a22, a32]);
$a3 = f64x4::from([a03, a13, a23, a33]);
}};
}
const fn sd_fft_basecase_0_1(_q: &FFTContext, _xs: &mut [f64]) {}
fn sd_fft_basecase_1_1(q: &FFTContext, xs: &mut [f64]) {
let n = q.p;
let ninv = q.pinv;
let x0 = f64_reduce_to_pm1n!(xs[0], n, ninv);
let x1 = f64_reduce_to_pm1n!(xs[1], n, ninv);
xs[0] = x0 + x1;
xs[1] = x0 - x1;
}
fn sd_fft_basecase_2_1(q: &FFTContext, xs: &mut [f64]) {
if let &mut [ref mut x0, ref mut x1, ref mut x2, ref mut x3, ..] = xs {
f64_length4_zero_j!(x0, x1, x2, x3, q.p, q.pinv, w2tab!(q, 1, 0));
}
}
fn sd_fft_basecase_3_1(q: &FFTContext, xs: &mut [f64]) {
if let &mut [
ref mut x0,
ref mut x1,
ref mut x2,
ref mut x3,
ref mut x4,
ref mut x5,
ref mut x6,
ref mut x7,
..,
] = xs
{
f64_length8_zero_j!(
x0,
x1,
x2,
x3,
x4,
x5,
x6,
x7,
q.p,
q.pinv,
w2tab!(q, 1, 0),
w2tab!(q, 2, 0),
w2tab!(q, 2, 1)
);
}
}
fn sd_fft_basecase_4_1(q: &FFTContext, xs: &mut [f64]) {
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let mut x0 = read_f64x4!(xs, 0);
let mut x1 = read_f64x4!(xs, 4);
let mut x2 = read_f64x4!(xs, 8);
let mut x3 = read_f64x4!(xs, 12);
const _: () = assert!(SD_FFT_CTX_W2TAB_INIT >= 4);
let iw = f64x4::splat(w2tab!(q, 0, 1));
f64x4_length4_zero_j!(x0, x1, x2, x3, n, ninv, iw);
let u = read_f64x4_w2tab!(q, 0, 0);
let v = read_f64x4_w2tab!(q, 0, 4);
let w = f64x4_unpack_lo_permute_0_2_1_3!(u, v);
let iw = f64x4_unpack_hi_permute_0_2_1_3!(u, v);
f64x4_transpose!(x0, x1, x2, x3);
length4_any_j!(x0, x1, x2, x3, n, ninv, u, w, iw);
write_f64x4!(xs, 0, x0);
write_f64x4!(xs, 4, x1);
write_f64x4!(xs, 8, x2);
write_f64x4!(xs, 12, x3);
}
fn sd_fft_basecase_4_0(q: &FFTContext, xs: &mut [f64], j_r: usize, j_bits: u64) {
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let mut x0 = read_f64x4!(xs, 0);
let mut x1 = read_f64x4!(xs, 4);
let mut x2 = read_f64x4!(xs, 8);
let mut x3 = read_f64x4!(xs, 12);
let j_bits = usize::exact_from(j_bits);
let w2 = f64x4::splat(w2tab!(q, j_bits, j_r));
let jp1 = j_bits + 1;
let j_2 = j_r << 1;
let w = f64x4::splat(w2tab!(q, jp1, j_2));
let iw = f64x4::splat(w2tab!(q, jp1, j_2 + 1));
length4_any_j!(x0, x1, x2, x3, n, ninv, w2, w, iw);
let jp3 = j_bits + 3;
let j_8 = j_r << 3;
let u = read_f64x4_w2tab!(q, jp3, j_8);
let v = read_f64x4_w2tab!(q, jp3, j_8 + 4);
let w2 = read_f64x4_w2tab!(q, j_bits + 2, j_r << 2);
let w = f64x4_unpack_lo_permute_0_2_1_3!(u, v);
let iw = f64x4_unpack_hi_permute_0_2_1_3!(u, v);
f64x4_transpose!(x0, x1, x2, x3);
length4_any_j!(x0, x1, x2, x3, n, ninv, w2, w, iw);
write_f64x4!(xs, 0, x0);
write_f64x4!(xs, 4, x1);
write_f64x4!(xs, 8, x2);
write_f64x4!(xs, 12, x3);
}
fn sd_fft_basecase_5_1(q: &FFTContext, xs: &mut [f64]) {
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let mut x0 = read_f64x4!(xs, 0);
let mut x1 = read_f64x4!(xs, 4);
let mut x2 = read_f64x4!(xs, 8);
let mut x3 = read_f64x4!(xs, 12);
let mut x4 = read_f64x4!(xs, 16);
let mut x5 = read_f64x4!(xs, 20);
let mut x6 = read_f64x4!(xs, 24);
let mut x7 = read_f64x4!(xs, 28);
let q00 = w2tab!(q, 0, 0);
let q10 = w2tab!(q, 1, 0);
let q20 = w2tab!(q, 2, 0);
let q21 = w2tab!(q, 2, 1);
let ww1 = f64x4::splat(q10);
let www2 = f64x4::splat(q20);
let www3 = f64x4::splat(q21);
f64x4_length8_zero_j!(x0, x1, x2, x3, x4, x5, x6, x7, n, ninv, ww1, www2, www3);
f64x4_transpose!(x0, x1, x2, x3);
f64x4_transpose!(x4, x5, x6, x7);
let mut w0 = f64x4::from([q00, q10, q20, q21]);
let mut ww0 = f64x4::from([q00, q20, w2tab!(q, 3, 0), w2tab!(q, 3, 2)]);
let mut ww1 = f64x4::from([q10, q21, w2tab!(q, 3, 1), w2tab!(q, 3, 3)]);
length4_any_j!(x0, x1, x2, x3, n, ninv, w0, ww0, ww1);
w0 = f64x4::from([w2tab!(q, 3, 0), w2tab!(q, 3, 1), w2tab!(q, 3, 2), w2tab!(q, 3, 3)]);
let u = read_f64x4_w2tab!(q, 4, 0);
let v = read_f64x4_w2tab!(q, 4, 4);
ww0 = f64x4_unpack_lo_permute_0_2_1_3!(u, v);
ww1 = f64x4_unpack_hi_permute_0_2_1_3!(u, v);
length4_any_j!(x4, x5, x6, x7, n, ninv, w0, ww0, ww1);
write_f64x4!(xs, 0, x0);
write_f64x4!(xs, 4, x1);
write_f64x4!(xs, 8, x2);
write_f64x4!(xs, 12, x3);
write_f64x4!(xs, 16, x4);
write_f64x4!(xs, 20, x5);
write_f64x4!(xs, 24, x6);
write_f64x4!(xs, 28, x7);
}
fn sd_fft_basecase_5_0(q: &FFTContext, xs: &mut [f64], j_r: usize, j_bits: u64) {
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let mut x0 = read_f64x4!(xs, 0);
let mut x1 = read_f64x4!(xs, 4);
let mut x2 = read_f64x4!(xs, 8);
let mut x3 = read_f64x4!(xs, 12);
let mut x4 = read_f64x4!(xs, 16);
let mut x5 = read_f64x4!(xs, 20);
let mut x6 = read_f64x4!(xs, 24);
let mut x7 = read_f64x4!(xs, 28);
let j_bits = usize::exact_from(j_bits);
let w0 = f64x4::splat(w2tab!(q, j_bits, j_r));
let jp1 = j_bits + 1;
let j_2 = j_r << 1;
let ww0 = f64x4::splat(w2tab!(q, jp1, j_2));
let ww1 = f64x4::splat(w2tab!(q, jp1, j_2 + 1));
let jp2 = j_bits + 2;
let j_4 = j_r << 2;
let www0 = f64x4::splat(w2tab!(q, jp2, j_4));
let www1 = f64x4::splat(w2tab!(q, jp2, j_4 + 1));
let www2 = f64x4::splat(w2tab!(q, jp2, j_4 + 2));
let www3 = f64x4::splat(w2tab!(q, jp2, j_4 + 3));
length8_any_j!(
x0, x1, x2, x3, x4, x5, x6, x7, n, ninv, w0, ww0, ww1, www0, www1, www2, www3
);
f64x4_transpose!(x0, x1, x2, x3);
f64x4_transpose!(x4, x5, x6, x7);
let jp3 = j_bits + 3;
let jp4 = j_bits + 4;
let j_8 = j_r << 3;
let w0 = read_f64x4_w2tab!(q, jp3, j_8);
let sixteen_j = j_r << 4;
let u = read_f64x4_w2tab!(q, jp4, sixteen_j);
let v = read_f64x4_w2tab!(q, jp4, sixteen_j + 4);
let ww0 = f64x4_unpack_lo_permute_0_2_1_3!(u, v);
let ww1 = f64x4_unpack_hi_permute_0_2_1_3!(u, v);
length4_any_j!(x0, x1, x2, x3, n, ninv, w0, ww0, ww1);
let w0 = read_f64x4_w2tab!(q, jp3, j_8 + 4);
let u = read_f64x4_w2tab!(q, jp4, sixteen_j + 8);
let v = read_f64x4_w2tab!(q, jp4, sixteen_j + 12);
let ww0 = f64x4_unpack_lo_permute_0_2_1_3!(u, v);
let ww1 = f64x4_unpack_hi_permute_0_2_1_3!(u, v);
length4_any_j!(x4, x5, x6, x7, n, ninv, w0, ww0, ww1);
write_f64x4!(xs, 0, x0);
write_f64x4!(xs, 4, x1);
write_f64x4!(xs, 8, x2);
write_f64x4!(xs, 12, x3);
write_f64x4!(xs, 16, x4);
write_f64x4!(xs, 20, x5);
write_f64x4!(xs, 24, x6);
write_f64x4!(xs, 28, x7);
}
macro_rules! sd_fft_basecase {
($n: expr, $f0: ident, $f1: ident, $fs0: ident, $fs1: ident) => {
fn $f1(q: &FFTContext, xs: &mut [f64]) {
const LIMIT: usize = 1 << ($n - 2);
radix_4_forward_param_j_is_z!(q, iw, n, ninv);
split_into_chunks_mut!(xs, LIMIT, [xs0, xs1, xs2], xs3);
process_4_3!(
radix_4_forward_moth_j_is_z,
LIMIT,
xs0,
xs1,
xs2,
xs3,
iw,
n,
ninv
);
$fs1(q, xs0);
$fs0(q, xs1, 0, 1);
$fs0(q, xs2, 0, 2);
$fs0(q, xs3, 1, 2);
}
fn $f0(q: &FFTContext, xs: &mut [f64], j_r: usize, j_bits: u64) {
const LIMIT: usize = 1 << ($n - 2);
radix_4_forward_param_j_is_nz!(q, j_r, j_bits, w, w2, iw, n, ninv);
split_into_chunks_mut!(xs, LIMIT, [xs0, xs1, xs2], xs3);
process_4_5!(
radix_4_forward_moth_j_is_nz,
LIMIT,
xs0,
xs1,
xs2,
xs3,
w,
w2,
iw,
n,
ninv
);
let j_r_4 = j_r << 2;
let j_bits_p_2 = j_bits + 2;
$fs0(q, xs0, j_r_4, j_bits_p_2);
$fs0(q, xs1, j_r_4 + 1, j_bits_p_2);
$fs0(q, xs2, j_r_4 + 2, j_bits_p_2);
$fs0(q, xs3, j_r_4 + 3, j_bits_p_2);
}
};
}
sd_fft_basecase!(
6,
sd_fft_basecase_6_0,
sd_fft_basecase_6_1,
sd_fft_basecase_4_0,
sd_fft_basecase_4_1
);
sd_fft_basecase!(
7,
sd_fft_basecase_7_0,
sd_fft_basecase_7_1,
sd_fft_basecase_5_0,
sd_fft_basecase_5_1
);
sd_fft_basecase!(
8,
sd_fft_basecase_8_0,
sd_fft_basecase_8_1,
sd_fft_basecase_6_0,
sd_fft_basecase_6_1
);
sd_fft_basecase!(
9,
sd_fft_basecase_9_0,
sd_fft_basecase_9_1,
sd_fft_basecase_7_0,
sd_fft_basecase_7_1
);
fn sd_fft_base_8_1(q: &FFTContext, xs: &mut [f64], j: usize) {
set_j_bits_and_j_r!(j, j_bits, j_r);
if j == 0 {
sd_fft_basecase_8_1(q, xs);
} else {
sd_fft_basecase_8_0(q, xs, j_r, j_bits);
}
}
fn sd_fft_base_8_0(q: &FFTContext, xs: &mut [f64], j: usize) {
assert_ne!(j, 0);
set_j_bits_and_j_r!(j, j_bits, j_r);
sd_fft_basecase_8_0(q, xs, j_r, j_bits);
}
fn sd_fft_base_9_1(q: &FFTContext, xs: &mut [f64], j: usize) {
set_j_bits_and_j_r!(j, j_bits, j_r);
if j == 0 {
sd_fft_basecase_9_1(q, xs);
} else {
sd_fft_basecase_9_0(q, xs, j_r, j_bits);
}
}
fn sd_fft_no_trunc_internal(
q: &FFTContext,
xs: &mut [f64],
s: usize,
k: u64,
j: usize,
) {
if k > 2 {
let k1 = k >> 1;
let k2 = k - k1;
let shift = s << k2;
let big_s = s << LG_BLK_SZ;
let mut m = 0;
for _ in 0..usize::power_of_2(k2) {
sd_fft_no_trunc_block(q, &mut xs[m..], shift, k1, j);
m += big_s;
}
let shift = k2 + LG_BLK_SZ;
let shifted = j << k1;
let big_s = s << shift;
let mut m = 0;
for b in 0..usize::power_of_2(k1) {
sd_fft_no_trunc_internal(q, &mut xs[m..], s, k2, shifted + b);
m += big_s;
}
return;
}
if k == 2 {
sd_fft_no_trunc_block(q, xs, s, 2, j);
split_into_chunks_mut!(xs, s << LG_BLK_SZ, [xs0, xs1, xs2], xs3);
let j_4 = j << 2;
sd_fft_base_8_1(q, xs0, j_4);
sd_fft_base_8_0(q, xs1, j_4 + 1);
sd_fft_base_8_0(q, xs2, j_4 + 2);
sd_fft_base_8_0(q, xs3, j_4 + 3);
} else if k == 1 {
sd_fft_base_9_1(q, xs, j);
} else {
sd_fft_base_8_1(q, xs, j);
}
}
fn sd_fft_moth_trunc_block_1<const ITRUNC: usize, const OTRUNC: usize>(
q: &FFTContext,
_j_r: usize,
_j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
radix_4_forward_param_j_is_z!(q, iw, n, ninv);
let gap_2 = gap << 1;
let gap_3 = gap_2 + gap;
for i in (0..BLK_SZ).step_by(BIG_N) {
let mut x0 = f64x8::default();
let mut x1 = f64x8::default();
let mut x2 = f64x8::default();
let mut x3 = f64x8::default();
if 0 < ITRUNC {
x0 = read_f64x8!(xs, i);
}
if 0 < ITRUNC {
x0 = f64x8_reduce_to_pm1n!(x0, n, ninv);
}
if 1 < ITRUNC {
x1 = read_f64x8!(xs, gap + i);
}
if 2 < ITRUNC {
x2 = read_f64x8!(xs, gap_2 + i);
}
if 2 < ITRUNC {
x2 = f64x8_reduce_to_pm1n!(x2, n, ninv);
}
if 3 < ITRUNC {
x3 = read_f64x8!(xs, gap_3 + i);
}
if 3 < ITRUNC {
x3 = f64x8_reduce_to_pm1n!(x3, n, ninv);
}
let y0 = if 2 < ITRUNC { x0 + x2 } else { x0 };
let mut y1 = if 3 < ITRUNC { x1 + x3 } else { x1 };
let y2 = if 2 < ITRUNC { x0 - x2 } else { x0 };
let mut y3 = if 3 < ITRUNC { x1 - x3 } else { x1 };
y1 = f64x8_reduce_to_pm1n!(y1, n, ninv);
y3 = f64x8_mulmod!(y3, iw, n, ninv);
x0 = y0 + y1;
x1 = y0 - y1;
x2 = y2 + y3;
x3 = y2 - y3;
if 0 < OTRUNC {
write_f64x8!(xs, i, x0);
}
if 1 < OTRUNC {
write_f64x8!(xs, gap + i, x1);
}
if 2 < OTRUNC {
write_f64x8!(xs, gap_2 + i, x2);
}
if 3 < OTRUNC {
write_f64x8!(xs, gap_3 + i, x3);
}
}
}
fn sd_fft_moth_trunc_block_0<const ITRUNC: usize, const OTRUNC: usize>(
q: &FFTContext,
j_r: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
radix_4_forward_param_j_is_nz!(q, j_r, j_bits, w, w2, iw, n, ninv);
let gap_2 = gap << 1;
let gap_3 = gap_2 + gap;
for i in (0..BLK_SZ).step_by(BIG_N) {
let mut x0 = f64x8::default();
let mut x1 = f64x8::default();
let mut x2 = f64x8::default();
let mut x3 = f64x8::default();
if 0 < ITRUNC {
x0 = read_f64x8!(xs, i);
}
if 0 < ITRUNC {
x0 = f64x8_reduce_to_pm1n!(x0, n, ninv);
}
if 1 < ITRUNC {
x1 = read_f64x8!(xs, gap + i);
}
if 2 < ITRUNC {
x2 = read_f64x8!(xs, gap_2 + i);
}
if 2 < ITRUNC {
x2 = f64x8_mulmod!(x2, w2, n, ninv);
}
if 3 < ITRUNC {
x3 = read_f64x8!(xs, gap_3 + i);
}
if 3 < ITRUNC {
x3 = f64x8_mulmod!(x3, w2, n, ninv);
}
let y0 = if 2 < ITRUNC { x0 + x2 } else { x0 };
let mut y1 = if 3 < ITRUNC { x1 + x3 } else { x1 };
let y2 = if 2 < ITRUNC { x0 - x2 } else { x0 };
let mut y3 = if 3 < ITRUNC { x1 - x3 } else { x1 };
y1 = f64x8_mulmod!(y1, w, n, ninv);
y3 = f64x8_mulmod!(y3, iw, n, ninv);
x0 = y0 + y1;
x1 = y0 - y1;
x2 = y2 + y3;
x3 = y2 - y3;
if 0 < OTRUNC {
write_f64x8!(xs, i, x0);
}
if 1 < OTRUNC {
write_f64x8!(xs, gap + i, x1);
}
if 2 < OTRUNC {
write_f64x8!(xs, gap_2 + i, x2);
}
if 3 < OTRUNC {
write_f64x8!(xs, gap_3 + i, x3);
}
}
}
type Sd2MothTruncBlockFn =
for<'a, 'b, 'c, 'd> fn(&'a FFTContext, usize, &'b mut [f64], &'c mut [f64]);
type Sd4MothTruncBlockFn = for<'a, 'b, 'c> fn(&'a FFTContext, usize, u64, &'b mut [f64], usize);
const SD_FFT_4_MOTH_TRUNC_BLOCK_TABLE: [Sd4MothTruncBlockFn; 24] = [
sd_fft_moth_trunc_block_0::<2, 1>,
sd_fft_moth_trunc_block_1::<2, 1>,
sd_fft_moth_trunc_block_0::<2, 2>,
sd_fft_moth_trunc_block_1::<2, 2>,
sd_fft_moth_trunc_block_0::<2, 3>,
sd_fft_moth_trunc_block_1::<2, 3>,
sd_fft_moth_trunc_block_0::<2, 4>,
sd_fft_moth_trunc_block_1::<2, 4>,
sd_fft_moth_trunc_block_0::<3, 1>,
sd_fft_moth_trunc_block_1::<3, 1>,
sd_fft_moth_trunc_block_0::<3, 2>,
sd_fft_moth_trunc_block_1::<3, 2>,
sd_fft_moth_trunc_block_0::<3, 3>,
sd_fft_moth_trunc_block_1::<3, 3>,
sd_fft_moth_trunc_block_0::<3, 4>,
sd_fft_moth_trunc_block_1::<3, 4>,
sd_fft_moth_trunc_block_0::<4, 1>,
sd_fft_moth_trunc_block_1::<4, 1>,
sd_fft_moth_trunc_block_0::<4, 2>,
sd_fft_moth_trunc_block_1::<4, 2>,
sd_fft_moth_trunc_block_0::<4, 3>,
sd_fft_moth_trunc_block_1::<4, 3>,
sd_fft_moth_trunc_block_0::<4, 4>,
sd_fft_moth_trunc_block_1::<4, 4>,
];
macro_rules! radix_2_forward_moth_trunc_2_1_j_is_z {
($x0: ident, $x1: ident, $n: ident, $ninv: ident) => {
$x0 = f64x8_reduce_to_pm1n!($x0, $n, $ninv);
$x1 = f64x8_reduce_to_pm1n!($x1, $n, $ninv);
$x0 += $x1;
};
}
macro_rules! radix_2_forward_moth_trunc_2_1_j_is_nz {
($x0: ident, $x1: ident, $w: ident, $n: ident, $ninv: ident) => {
$x0 = f64x8_reduce_to_pm1n!($x0, $n, $ninv);
$x1 = f64x8_mulmod!($x1, $w, $n, $ninv);
$x0 += $x1;
};
}
fn sd_fft_trunc_block(
q: &FFTContext,
xs: &mut [f64],
s: usize,
k: u64,
j: usize,
itrunc: usize,
otrunc: usize,
) {
let pow = usize::power_of_2(k);
assert!(itrunc <= pow);
assert!(otrunc <= pow);
if otrunc < 1 {
fail_on_untested_path("sd_fft_trunc_block, otrunc < 1");
return;
}
if itrunc <= 1 {
let big_s = s << LG_BLK_SZ;
if itrunc < 1 {
fail_on_untested_path("sd_fft_trunc_block, itrunc < 1");
for c in xs[..big_s * otrunc].chunks_mut(big_s) {
c[..BLK_SZ].fill(0.0);
}
} else {
let mut m = big_s;
for _ in 1..otrunc {
let (xs_lo, xs_hi) = xs.split_at_mut(m);
xs_hi[..BLK_SZ].copy_from_slice(&xs_lo[..BLK_SZ]);
m += big_s;
}
}
return;
}
if itrunc == otrunc && otrunc == pow {
sd_fft_no_trunc_block(q, xs, s, k, j);
return;
}
let big_s = s << LG_BLK_SZ;
if k > 2 {
let k1 = k >> 1;
let k2 = k - k1;
let l2 = usize::power_of_2(k2);
let n1 = otrunc >> k2;
let mask = l2 - 1;
let n2 = otrunc & mask;
let z1 = itrunc >> k2;
let z2 = itrunc & mask;
let n1p = n1 + usize::from(n2 != 0);
let z2p = min(l2, itrunc);
let shifted = s << k2;
let mut m = 0;
for a in 0..z2p {
sd_fft_trunc_block(
q,
&mut xs[m..],
shifted,
k1,
j,
z1 + usize::from(a < z2),
n1p,
);
m += big_s;
}
let big_s_2 = s << (k2 + LG_BLK_SZ);
let shifted = j << k1;
let mut m = 0;
for b in 0..n1 {
sd_fft_trunc_block(q, &mut xs[m..], s, k2, shifted + b, z2p, l2);
m += big_s_2;
}
if n2 != 0 {
sd_fft_trunc_block(q, &mut xs[n1 * big_s_2..], s, k2, shifted + n1, z2p, n2);
}
return;
}
set_j_bits_and_j_r!(j, j_bits, j_r);
if k == 2 {
let index = usize::from(j == 0) + ((otrunc - 1 + ((itrunc - 2) << 2)) << 1);
SD_FFT_4_MOTH_TRUNC_BLOCK_TABLE[index](q, j_r, j_bits, xs, big_s);
} else if k == 1 {
let (xs0, xs1) = xs.split_at_mut(big_s);
assert_eq!(itrunc, 2);
assert_eq!(otrunc, 1);
if j_bits == 0 {
fail_on_untested_path("sd_fft_trunc_block, j_bits == 0");
radix_2_forward_param_j_is_z!(q, n, ninv);
process_2_2!(
radix_2_forward_moth_trunc_2_1_j_is_z,
BLK_SZ,
xs0,
xs1,
n,
ninv
);
} else {
radix_2_forward_param_j_is_nz!(q, j_r, j_bits, w, n, ninv);
process_2_3!(
radix_2_forward_moth_trunc_2_1_j_is_nz,
BLK_SZ,
xs0,
xs1,
w,
n,
ninv
);
}
}
}
fn sd_fft_trunc_internal(
q: &FFTContext,
xs: &mut [f64],
s: usize,
k: u64,
j: usize,
itrunc: usize,
otrunc: usize,
) {
if otrunc < 1 {
fail_on_untested_path("sd_fft_trunc_internal, otrunc < 1");
return;
}
if itrunc < 1 {
fail_on_untested_path("sd_fft_trunc_internal, itrunc < 1");
let big_s = s << LG_BLK_SZ;
for c in xs[..big_s * otrunc].chunks_mut(big_s) {
c[..BLK_SZ].fill(0.0);
}
return;
}
if itrunc == otrunc && otrunc == usize::power_of_2(k) {
sd_fft_no_trunc_internal(q, xs, s, k, j);
return;
}
let big_s = s << LG_BLK_SZ;
if k > 2 {
let k1 = k >> 1;
let k2 = k - k1;
let l2 = usize::power_of_2(k2);
let n1 = otrunc >> k2;
let mask = l2 - 1;
let n2 = otrunc & mask;
let z1 = itrunc >> k2;
let z2 = itrunc & mask;
let n1p = n1 + usize::from(n2 != 0);
let z2p = min(l2, itrunc);
let shifted = s << k2;
let mut m = 0;
for a in 0..z2p {
sd_fft_trunc_block(
q,
&mut xs[m..],
shifted,
k1,
j,
z1 + usize::from(a < z2),
n1p,
);
m += big_s;
}
let big_s_2 = s << (k2 + LG_BLK_SZ);
let shifted = j << k1;
let mut m = 0;
for b in 0..n1 {
sd_fft_trunc_internal(q, &mut xs[m..], s, k2, shifted + b, z2p, l2);
m += big_s_2;
}
if n2 > 0 {
sd_fft_trunc_internal(q, &mut xs[n1 * big_s_2..], s, k2, shifted + n1, z2p, n2);
}
return;
}
if k == 2 {
sd_fft_trunc_block(q, xs, s, 2, j, itrunc, otrunc);
let j_4 = j << 2;
sd_fft_base_8_1(q, xs, j_4);
if otrunc > 1 {
sd_fft_base_8_0(q, &mut xs[big_s..], j_4 + 1);
}
if otrunc > 2 {
sd_fft_base_8_0(q, &mut xs[big_s << 1..], j_4 + 2);
}
if otrunc > 3 {
sd_fft_base_8_0(q, &mut xs[big_s * 3..], j_4 + 3);
}
} else if k == 1 {
sd_fft_trunc_block(q, xs, s, 1, j, itrunc, otrunc);
let j_2 = j << 1;
sd_fft_base_8_1(q, xs, j_2);
if otrunc > 1 {
sd_fft_base_8_0(q, &mut xs[big_s..], j_2 + 1);
}
} else {
sd_fft_base_8_1(q, xs, j);
}
}
fn sd_fft_ctx_fit_depth_with_lock(q: &mut FFTContext, depth: u64) {
for k in q.w2tab_depth..depth {
let ww = q
.primitive_root
.mod_pow((q.mod_data.n - 1) >> (k + 1), q.mod_data.n);
let w = f64x8::splat(f64_reduce_0n_to_pmhn!(ww as f64, q.p));
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let big_n = usize::power_of_2(k - 1);
let old_len = q.w2tab_backing.len();
q.w2tab_backing.resize(
old_len + big_n.round_to_multiple_of_power_of_2(12, Ceiling).0,
0.0,
);
let ku = k as usize;
q.w2tab_offsets[ku] = old_len;
let mut off = 0;
let mut l = const { 1 << (SD_FFT_CTX_W2TAB_INIT - 1) };
let mut kk = 0;
for j in SD_FFT_CTX_W2TAB_INIT - 1..k {
for i in (0..l).step_by(16) {
let x0 = read_f64x8_w2tab!(q, kk, i);
let x1 = read_f64x8_w2tab!(q, kk, i + 8);
let y0 = f64x8_reduce_pm1n_to_pmhn!(f64x8_mulmod!(x0, w, n, ninv), n);
let y1 = f64x8_reduce_pm1n_to_pmhn!(f64x8_mulmod!(x1, w, n, ninv), n);
write_f64x8_w2tab!(q, ku, off + i, y0);
write_f64x8_w2tab!(q, ku, off + i + 8, y1);
}
kk = j as usize + 1;
l += off;
off = l;
}
q.w2tab_depth = k;
}
}
fn sd_fft_ctx_fit_depth(q: &mut FFTContext, depth: u64) {
if q.w2tab_depth < depth {
sd_fft_ctx_fit_depth_with_lock(q, depth);
}
}
fn sd_fft_trunc(
q: &mut FFTContext,
ds: &mut [f64],
l: u64,
itrunc: usize,
otrunc: usize,
) {
let pow = usize::power_of_2(l);
assert!(itrunc <= pow);
assert!(otrunc <= pow);
if l > LG_BLK_SZ {
sd_fft_ctx_fit_depth(q, l);
let new_itrunc = itrunc.div_round(BLK_SZ, Ceiling).0;
let new_otrunc = otrunc.div_round(BLK_SZ, Ceiling).0;
ds[itrunc..][..itrunc.wrapping_neg() & const { BLK_SZ - 1 }].fill(0.0);
sd_fft_trunc_internal(q, ds, 1, l - LG_BLK_SZ, 0, new_itrunc, new_otrunc);
return;
}
fail_on_untested_path("sd_fft_trunc, l <= LG_BLK_SZ");
ds[itrunc..][..usize::power_of_2(l)].fill(0.0);
const _: () = assert!(LG_BLK_SZ <= SD_FFT_CTX_W2TAB_INIT);
match l {
0 => sd_fft_basecase_0_1(q, ds),
1 => sd_fft_basecase_1_1(q, ds),
2 => sd_fft_basecase_2_1(q, ds),
3 => sd_fft_basecase_3_1(q, ds),
4 => sd_fft_basecase_4_1(q, ds),
5 => sd_fft_basecase_5_1(q, ds),
6 => sd_fft_basecase_6_1(q, ds),
7 => sd_fft_basecase_7_1(q, ds),
8 => sd_fft_basecase_8_1(q, ds),
_ => unreachable!(),
}
}
macro_rules! set_j_bits_and_j_mr {
($j: ident, $j_bits: ident, $j_mr: ident) => {
let ($j_bits, $j_mr) = if $j == 0 {
(0, 0)
} else {
let j_bits = n_nbits_nz!($j);
(j_bits, usize::power_of_2(j_bits) - 1 - $j)
};
};
}
macro_rules! radix_4_reverse_param_j_is_z {
($q: ident, $iw: ident, $n: ident, $ninv: ident) => {
let $iw = f64x8::splat(w2tab!($q, 0, 1));
let $n = f64x8::splat($q.p);
let $ninv = f64x8::splat($q.pinv);
};
}
macro_rules! radix_4_reverse_param_j_is_nz {
(
$q: ident,
$j_mr: ident,
$j_bits: ident,
$w: ident,
$w2: ident,
$iw: ident,
$n: ident,
$ninv: ident
) => {
let j_bits = usize::exact_from($j_bits);
let jp1 = j_bits + 1;
let j_2 = $j_mr << 1;
let $w = f64x8::splat(w2tab!($q, jp1, j_2 + 1));
let $w2 = f64x8::splat(w2tab!($q, j_bits, $j_mr));
let $iw = f64x8::splat(w2tab!($q, jp1, j_2));
let $n = f64x8::splat($q.p);
let $ninv = f64x8::splat($q.pinv);
};
}
macro_rules! radix_4_reverse_moth_j_is_z {
($x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$iw: ident,
$n: ident,
$ninv: ident
) => {
let mut y0 = $x0 + $x1;
let mut y1 = $x2 + $x3;
let mut y2 = $x0 - $x1;
let mut y3 = $x2 - $x3;
y0 = f64x8_reduce_to_pm1n!(y0, $n, $ninv);
y1 = f64x8_reduce_to_pm1n!(y1, $n, $ninv);
y2 = f64x8_reduce_to_pm1n!(y2, $n, $ninv);
y3 = f64x8_mulmod!(y3, $iw, $n, $ninv);
$x0 = y0 + y1;
$x2 = y0 - y1;
$x1 = y2 - y3;
$x3 = y2 + y3;
};
}
macro_rules! radix_4_reverse_moth_j_is_nz {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$w: ident,
$w2: ident,
$iw: ident,
$n: ident,
$ninv: ident
) => {
let y0 = $x0 + $x1;
let y1 = $x2 + $x3;
let y2 = f64x8_mulmod!($x0 - $x1, $w, $n, $ninv);
let y3 = f64x8_mulmod!($x3 - $x2, $iw, $n, $ninv);
$x0 = y0 + y1;
$x1 = y3 - y2;
$x2 = y1 - y0;
$x3 = y3 + y2;
$x0 = f64x8_reduce_to_pm1n!($x0, $n, $ninv);
$x2 = f64x8_mulmod!($x2, $w2, $n, $ninv);
$x3 = f64x8_mulmod!($x3, $w2, $n, $ninv);
};
}
macro_rules! radix_2_reverse_param_j_is_z {
($q: ident, $n: ident, $ninv: ident) => {
let $n = f64x8::splat($q.p);
let $ninv = f64x8::splat($q.pinv);
};
}
macro_rules! radix_2_reverse_param_j_is_nz {
(
$q: ident,
$j_mr: ident,
$j_bits: ident,
$w: ident,
$n: ident,
$ninv: ident
) => {
let $w = f64x8::splat(w2tab!($q, usize::exact_from($j_bits), $j_mr));
let $n = f64x8::splat($q.p);
let $ninv = f64x8::splat($q.pinv);
};
}
macro_rules! radix_2_reverse_moth_j_is_z {
($x0: ident, $x1: ident, $n: ident, $ninv: ident) => {
let y0 = f64x8_reduce_to_pm1n!($x0 + $x1, $n, $ninv);
let y1 = f64x8_reduce_to_pm1n!($x0 - $x1, $n, $ninv);
$x0 = y0;
$x1 = y1;
};
}
macro_rules! radix_2_reverse_moth_j_is_nz {
($x0: ident, $x1: ident, $w: ident, $n: ident, $ninv: ident) => {
let y0 = f64x8_reduce_to_pm1n!($x0 + $x1, $n, $ninv);
let y1 = f64x8_mulmod!($x1 - $x0, $w, $n, $ninv);
$x0 = y0;
$x1 = y1;
};
}
fn sd_ifft_no_trunc_block(
q: &FFTContext,
xs: &mut [f64],
s: usize,
k: u64,
j: usize,
) {
let big_s = s << LG_BLK_SZ;
if k > 2 {
let k1 = k >> 1;
let k2 = k - k1;
let big_s_2 = s << (k2 + LG_BLK_SZ);
let shifted = j << k1;
let mut m = 0;
for b in 0..usize::power_of_2(k1) {
sd_ifft_no_trunc_block(q, &mut xs[m..], s, k2, shifted + b);
m += big_s_2;
}
let shifted = s << k2;
let mut m = 0;
for _ in 0..usize::power_of_2(k2) {
sd_ifft_no_trunc_block(q, &mut xs[m..], shifted, k1, j);
m += big_s;
}
return;
}
set_j_bits_and_j_mr!(j, j_bits, j_mr);
if k == 2 {
split_into_chunks_mut!(xs, big_s, [xs0, xs1, xs2], xs3);
if j_bits == 0 {
radix_4_reverse_param_j_is_z!(q, iw, n, ninv);
process_4_3!(
radix_4_reverse_moth_j_is_z,
BLK_SZ,
xs0,
xs1,
xs2,
xs3,
iw,
n,
ninv
);
} else {
radix_4_reverse_param_j_is_nz!(q, j_mr, j_bits, w, w2, iw, n, ninv);
process_4_5!(
radix_4_reverse_moth_j_is_nz,
BLK_SZ,
xs0,
xs1,
xs2,
xs3,
w,
w2,
iw,
n,
ninv
);
}
} else if k == 1 {
let (xs0, xs1) = xs.split_at_mut(big_s);
if j_bits == 0 {
radix_2_reverse_param_j_is_z!(q, n, ninv);
process_2_2!(radix_2_reverse_moth_j_is_z, BLK_SZ, xs0, xs1, n, ninv);
} else {
radix_2_reverse_param_j_is_nz!(q, j_mr, j_bits, w, n, ninv);
process_2_3!(radix_2_reverse_moth_j_is_nz, BLK_SZ, xs0, xs1, w, n, ninv);
}
}
}
macro_rules! f64_length2inv_zero_j_mut {
($x0: ident, $x1: ident, $n: expr, $ninv: expr) => {
(*$x0, *$x1) = (
f64_reduce_to_pm1n!(*$x0 + *$x1, $n, $ninv),
f64_reduce_to_pm1n!(*$x0 - *$x1, $n, $ninv),
);
};
}
macro_rules! f64x4_length2inv_zero_j {
($x0: ident, $x1: ident, $n: ident, $ninv: ident) => {
($x0, $x1) = (
f64x4_reduce_to_pm1n!($x0 + $x1, $n, $ninv),
f64x4_reduce_to_pm1n!($x0 - $x1, $n, $ninv),
);
};
}
macro_rules! f64_length2inv_any_j_mut {
($x0: ident, $x1: ident, $n: ident, $ninv: ident, $w0: expr) => {
(*$x0, *$x1) = (
f64_reduce_to_pm1n!(*$x0 + *$x1, $n, $ninv),
f64_mulmod!(*$x1 - *$x0, $w0, $n, $ninv),
);
};
}
macro_rules! f64x4_length2inv_any_j {
($x0: ident, $x1: ident, $n: ident, $ninv: ident, $w0: ident) => {
($x0, $x1) = (
f64x4_reduce_to_pm1n!($x0 + $x1, $n, $ninv),
f64x4_mulmod!($x1 - $x0, $w0, $n, $ninv),
);
};
}
macro_rules! f64_length4inv_zero_j_mut {
($x0: ident, $x1: ident, $x2: ident, $x3: ident, $n: expr, $ninv: expr, $ww1: expr) => {
let n = $n;
let ninv = $ninv;
let y0 = f64_reduce_to_pm1n!(*$x0 + *$x1, n, ninv);
let y1 = f64_reduce_to_pm1n!(*$x2 + *$x3, n, ninv);
let y2 = f64_reduce_to_pm1n!(*$x0 - *$x1, n, ninv);
let y3 = f64_mulmod!(*$x2 - *$x3, $ww1, n, ninv);
*$x0 = y0 + y1;
*$x2 = y0 - y1;
*$x1 = y2 - y3;
*$x3 = y2 + y3;
};
}
macro_rules! f64x4_length4inv_zero_j {
($x0: ident, $x1: ident, $x2: ident, $x3: ident, $n: ident, $ninv: ident, $ww1: ident) => {
let y0 = f64x4_reduce_to_pm1n!($x0 + $x1, $n, $ninv);
let y1 = f64x4_reduce_to_pm1n!($x2 + $x3, $n, $ninv);
let y2 = f64x4_reduce_to_pm1n!($x0 - $x1, $n, $ninv);
let y3 = f64x4_mulmod!($x2 - $x3, $ww1, $n, $ninv);
$x0 = y0 + y1;
$x2 = y0 - y1;
$x1 = y2 - y3;
$x3 = y2 + y3;
};
}
macro_rules! f64x4_length4inv_any_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$n: expr,
$ninv: ident,
$w0: ident,
$ww0: ident,
$ww1: ident
) => {
let y0 = $x0 + $x1;
let y1 = $x2 + $x3;
let y2 = f64x4_mulmod!($x0 - $x1, $ww0, $n, $ninv);
let y3 = f64x4_mulmod!($x3 - $x2, $ww1, $n, $ninv);
$x0 = f64x4_reduce_to_pm1n!(y0 + y1, $n, $ninv);
$x1 = y3 - y2;
$x2 = f64x4_mulmod!(y1 - y0, $w0, $n, $ninv);
$x3 = f64x4_mulmod!(y3 + y2, $w0, $n, $ninv);
};
}
macro_rules! f64_length8inv_zero_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$x4: ident,
$x5: ident,
$x6: ident,
$x7: ident,
$n: expr,
$ninv: expr,
$ww1: expr,
$www2: expr,
$www3: expr
) => {
let n = $n;
let ninv = $ninv;
let ww1 = $ww1;
f64_length2inv_zero_j_mut!($x0, $x1, n, ninv);
f64_length2inv_any_j_mut!($x2, $x3, n, ninv, ww1);
f64_length2inv_any_j_mut!($x4, $x5, n, ninv, $www2);
f64_length2inv_any_j_mut!($x6, $x7, n, ninv, $www3);
f64_length4inv_zero_j_mut!($x0, $x2, $x4, $x6, n, ninv, ww1);
f64_length4inv_zero_j_mut!($x1, $x3, $x5, $x7, n, ninv, ww1);
};
}
macro_rules! f64x4_length8inv_zero_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$x4: ident,
$x5: ident,
$x6: ident,
$x7: ident,
$n: ident,
$ninv: ident,
$ww1: ident,
$www2: ident,
$www3: ident
) => {
f64x4_length2inv_zero_j!($x0, $x1, $n, $ninv);
f64x4_length2inv_any_j!($x2, $x3, $n, $ninv, $ww1);
f64x4_length2inv_any_j!($x4, $x5, $n, $ninv, $www2);
f64x4_length2inv_any_j!($x6, $x7, $n, $ninv, $www3);
f64x4_length4inv_zero_j!($x0, $x2, $x4, $x6, $n, $ninv, $ww1);
f64x4_length4inv_zero_j!($x1, $x3, $x5, $x7, $n, $ninv, $ww1);
};
}
macro_rules! f64x4_length8inv_any_j {
(
$x0: ident,
$x1: ident,
$x2: ident,
$x3: ident,
$x4: ident,
$x5: ident,
$x6: ident,
$x7: ident,
$n: ident,
$ninv: ident,
$w0: ident,
$ww0: ident,
$ww1: ident,
$www0: ident,
$www1: ident,
$www2: ident,
$www3: ident
) => {
f64x4_length2inv_any_j!($x0, $x1, $n, $ninv, $www0);
f64x4_length2inv_any_j!($x2, $x3, $n, $ninv, $www1);
f64x4_length2inv_any_j!($x4, $x5, $n, $ninv, $www2);
f64x4_length2inv_any_j!($x6, $x7, $n, $ninv, $www3);
f64x4_length4inv_any_j!($x0, $x2, $x4, $x6, $n, $ninv, $w0, $ww0, $ww1);
f64x4_length4inv_any_j!($x1, $x3, $x5, $x7, $n, $ninv, $w0, $ww0, $ww1);
};
}
const fn sd_ifft_basecase_0_1(_q: &FFTContext, _xs: &mut [f64]) {}
fn sd_ifft_basecase_1_1(q: &FFTContext, xs: &mut [f64]) {
if let &mut [ref mut x0, ref mut x1, ..] = xs {
f64_length2inv_zero_j_mut!(x0, x1, q.p, q.pinv);
}
}
fn sd_ifft_basecase_2_1(q: &FFTContext, xs: &mut [f64]) {
if let &mut [ref mut x0, ref mut x1, ref mut x2, ref mut x3, ..] = xs {
f64_length4inv_zero_j_mut!(x0, x1, x2, x3, q.p, q.pinv, w2tab!(q, 1, 0));
}
}
fn sd_ifft_basecase_3_1(q: &FFTContext, xs: &mut [f64]) {
if let &mut [
ref mut x0,
ref mut x1,
ref mut x2,
ref mut x3,
ref mut x4,
ref mut x5,
ref mut x6,
ref mut x7,
..,
] = xs
{
f64_length8inv_zero_j!(
x0,
x1,
x2,
x3,
x4,
x5,
x6,
x7,
q.p,
q.pinv,
w2tab!(q, 1, 0),
w2tab!(q, 2, 1),
w2tab!(q, 2, 0)
);
}
}
fn sd_ifft_basecase_4_1(q: &FFTContext, xs: &mut [f64]) {
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let mut x0 = read_f64x4!(xs, 0);
let mut x1 = read_f64x4!(xs, 4);
let mut x2 = read_f64x4!(xs, 8);
let mut x3 = read_f64x4!(xs, 12);
let q00 = w2tab!(q, 0, 0);
let q01 = w2tab!(q, 0, 1);
let q02 = w2tab!(q, 0, 2);
let q03 = w2tab!(q, 0, 3);
let w = f64x4::from([-q00, q03, w2tab!(q, 0, 7), w2tab!(q, 0, 5)]);
let iw = f64x4::from([q01, q02, w2tab!(q, 0, 6), w2tab!(q, 0, 4)]);
let w2 = f64x4::from([-q00, q01, q03, q02]);
f64x4_length4inv_any_j!(x0, x1, x2, x3, n, ninv, w2, w, iw);
f64x4_transpose!(x0, x1, x2, x3);
let iw = f64x4::splat(q01);
f64x4_length4inv_zero_j!(x0, x1, x2, x3, n, ninv, iw);
write_f64x4!(xs, 0, x0);
write_f64x4!(xs, 4, x1);
write_f64x4!(xs, 8, x2);
write_f64x4!(xs, 12, x3);
}
fn sd_ifft_basecase_4_0(q: &FFTContext, xs: &mut [f64], j_mr: usize, j_bits: u64) {
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let mut x0 = read_f64x4!(xs, 0);
let mut x1 = read_f64x4!(xs, 4);
let mut x2 = read_f64x4!(xs, 8);
let mut x3 = read_f64x4!(xs, 12);
let j_bits = usize::exact_from(j_bits);
let w2 = read_f64x4_w2tab!(q, j_bits + 2, j_mr << 2);
let [w20, w21, w22, w23] = w2.to_array();
let w2 = f64x4::from([w23, w22, w21, w20]);
let jp3 = j_bits + 3;
let j_8 = j_mr << 3;
let [u0, u1, u2, u3] = read_f64x4_w2tab!(q, jp3, j_8).to_array();
let [v0, v1, v2, v3] = read_f64x4_w2tab!(q, jp3, j_8 + 4).to_array();
let mut w = f64x4::from([v3, v1, u3, u1]);
let mut iw = f64x4::from([v2, v0, u2, u0]);
f64x4_length4inv_any_j!(x0, x1, x2, x3, n, ninv, w2, w, iw);
f64x4_transpose!(x0, x1, x2, x3);
let jp1 = j_bits + 1;
let j_2 = j_mr << 1;
w = f64x4::splat(w2tab!(q, jp1, j_2 + 1));
iw = f64x4::splat(w2tab!(q, jp1, j_2));
let w2 = f64x4::splat(w2tab!(q, j_bits, j_mr));
f64x4_length4inv_any_j!(x0, x1, x2, x3, n, ninv, w2, w, iw);
write_f64x4!(xs, 0, x0);
write_f64x4!(xs, 4, x1);
write_f64x4!(xs, 8, x2);
write_f64x4!(xs, 12, x3);
}
fn sd_ifft_basecase_5_1(q: &FFTContext, xs: &mut [f64]) {
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let mut x0 = read_f64x4!(xs, 0);
let mut x1 = read_f64x4!(xs, 4);
let mut x2 = read_f64x4!(xs, 8);
let mut x3 = read_f64x4!(xs, 12);
let mut x4 = read_f64x4!(xs, 16);
let mut x5 = read_f64x4!(xs, 20);
let mut x6 = read_f64x4!(xs, 24);
let mut x7 = read_f64x4!(xs, 28);
let q00 = w2tab!(q, 0, 0);
let q10 = w2tab!(q, 1, 0);
let q20 = w2tab!(q, 2, 0);
let q21 = w2tab!(q, 2, 1);
let q30 = w2tab!(q, 3, 0);
let q31 = w2tab!(q, 3, 1);
let q32 = w2tab!(q, 3, 2);
let q33 = w2tab!(q, 3, 3);
let mut w0 = f64x4::from([-q00, q10, q21, q20]);
let mut ww0 = f64x4::from([-q00, q21, q33, q31]);
let mut ww1 = f64x4::from([q10, q20, q32, q30]);
f64x4_length4inv_any_j!(x0, x1, x2, x3, n, ninv, w0, ww0, ww1);
w0 = f64x4::from([q33, q32, q31, q30]);
ww0 = f64x4::from([w2tab!(q, 4, 7), w2tab!(q, 4, 5), w2tab!(q, 4, 3), w2tab!(q, 4, 1)]);
ww1 = f64x4::from([w2tab!(q, 4, 6), w2tab!(q, 4, 4), w2tab!(q, 4, 2), w2tab!(q, 4, 0)]);
f64x4_length4inv_any_j!(x4, x5, x6, x7, n, ninv, w0, ww0, ww1);
f64x4_transpose!(x0, x1, x2, x3);
f64x4_transpose!(x4, x5, x6, x7);
ww1 = f64x4::splat(q10);
let www2 = f64x4::splat(q21);
let www3 = f64x4::splat(q20);
f64x4_length8inv_zero_j!(x0, x1, x2, x3, x4, x5, x6, x7, n, ninv, ww1, www2, www3);
write_f64x4!(xs, 0, x0);
write_f64x4!(xs, 4, x1);
write_f64x4!(xs, 8, x2);
write_f64x4!(xs, 12, x3);
write_f64x4!(xs, 16, x4);
write_f64x4!(xs, 20, x5);
write_f64x4!(xs, 24, x6);
write_f64x4!(xs, 28, x7);
}
fn sd_ifft_basecase_5_0(q: &FFTContext, xs: &mut [f64], j_mr: usize, j_bits: u64) {
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let mut x0 = read_f64x4!(xs, 0);
let mut x1 = read_f64x4!(xs, 4);
let mut x2 = read_f64x4!(xs, 8);
let mut x3 = read_f64x4!(xs, 12);
let mut x4 = read_f64x4!(xs, 16);
let mut x5 = read_f64x4!(xs, 20);
let mut x6 = read_f64x4!(xs, 24);
let mut x7 = read_f64x4!(xs, 28);
let j_bits = usize::exact_from(j_bits);
let j_8 = j_mr << 3;
let jp3 = j_bits + 3;
let [w00, w01, w02, w03] = read_f64x4_w2tab!(q, jp3, j_8 + 4).to_array();
let w0 = f64x4::from([w03, w02, w01, w00]);
let jp4 = j_bits + 4;
let j_16 = j_mr << 4;
let [u0, u1, u2, u3] = read_f64x4_w2tab!(q, jp4, j_16 + 8).to_array();
let [v0, v1, v2, v3] = read_f64x4_w2tab!(q, jp4, j_16 + 12).to_array();
let mut ww0 = f64x4::from([v3, v1, u3, u1]);
let mut ww1 = f64x4::from([v2, v0, u2, u0]);
f64x4_length4inv_any_j!(x0, x1, x2, x3, n, ninv, w0, ww0, ww1);
let [w00, w01, w02, w03] = read_f64x4_w2tab!(q, jp3, j_8).to_array();
let w0 = f64x4::from([w03, w02, w01, w00]);
let [u0, u1, u2, u3] = read_f64x4_w2tab!(q, jp4, j_16).to_array();
let [v0, v1, v2, v3] = read_f64x4_w2tab!(q, jp4, j_16 + 4).to_array();
ww0 = f64x4::from([v3, v1, u3, u1]);
ww1 = f64x4::from([v2, v0, u2, u0]);
f64x4_length4inv_any_j!(x4, x5, x6, x7, n, ninv, w0, ww0, ww1);
f64x4_transpose!(x0, x1, x2, x3);
f64x4_transpose!(x4, x5, x6, x7);
let w0 = f64x4::splat(w2tab!(q, j_bits, j_mr));
let jp1 = j_bits + 1;
let j_2 = j_mr << 1;
ww0 = f64x4::splat(w2tab!(q, jp1, j_2 + 1));
ww1 = f64x4::splat(w2tab!(q, jp1, j_2));
let jp2 = j_bits + 2;
let j_4 = j_mr << 2;
let www0 = f64x4::splat(w2tab!(q, jp2, j_4 + 3));
let www1 = f64x4::splat(w2tab!(q, jp2, j_4 + 2));
let www2 = f64x4::splat(w2tab!(q, jp2, j_4 + 1));
let www3 = f64x4::splat(w2tab!(q, jp2, j_4));
f64x4_length8inv_any_j!(
x0, x1, x2, x3, x4, x5, x6, x7, n, ninv, w0, ww0, ww1, www0, www1, www2, www3
);
write_f64x4!(xs, 0, x0);
write_f64x4!(xs, 4, x1);
write_f64x4!(xs, 8, x2);
write_f64x4!(xs, 12, x3);
write_f64x4!(xs, 16, x4);
write_f64x4!(xs, 20, x5);
write_f64x4!(xs, 24, x6);
write_f64x4!(xs, 28, x7);
}
macro_rules! sd_ifft_basecase {
($n: expr, $f0: ident, $f1: ident, $fs0: ident, $fs1: ident) => {
fn $f1(q: &FFTContext, xs: &mut [f64]) {
const LIMIT: usize = 1 << ($n - 2);
split_into_chunks_mut!(xs, LIMIT, [xs0, xs1, xs2], xs3);
$fs1(q, xs0);
$fs0(q, xs1, 0, 1);
$fs0(q, xs2, 1, 2);
$fs0(q, xs3, 0, 2);
radix_4_reverse_param_j_is_z!(q, iw, n, ninv);
process_4_3!(
radix_4_reverse_moth_j_is_z,
LIMIT,
xs0,
xs1,
xs2,
xs3,
iw,
n,
ninv
);
}
fn $f0(q: &FFTContext, xs: &mut [f64], j_mr: usize, j_bits: u64) {
const LIMIT: usize = 1 << ($n - 2);
assert_ne!(j_bits, 0);
split_into_chunks_mut!(xs, LIMIT, [xs0, xs1, xs2], xs3);
let j_mr_4 = j_mr << 2;
let j_bits_p_2 = j_bits + 2;
$fs0(q, xs0, j_mr_4 + 3, j_bits_p_2);
$fs0(q, xs1, j_mr_4 + 2, j_bits_p_2);
$fs0(q, xs2, j_mr_4 + 1, j_bits_p_2);
$fs0(q, xs3, j_mr_4, j_bits_p_2);
radix_4_reverse_param_j_is_nz!(q, j_mr, j_bits, w, w2, iw, n, ninv);
process_4_5!(
radix_4_reverse_moth_j_is_nz,
LIMIT,
xs0,
xs1,
xs2,
xs3,
w,
w2,
iw,
n,
ninv
);
}
};
}
sd_ifft_basecase!(
6,
sd_ifft_basecase_6_0,
sd_ifft_basecase_6_1,
sd_ifft_basecase_4_0,
sd_ifft_basecase_4_1
);
sd_ifft_basecase!(
7,
sd_ifft_basecase_7_0,
sd_ifft_basecase_7_1,
sd_ifft_basecase_5_0,
sd_ifft_basecase_5_1
);
sd_ifft_basecase!(
8,
sd_ifft_basecase_8_0,
sd_ifft_basecase_8_1,
sd_ifft_basecase_6_0,
sd_ifft_basecase_6_1
);
sd_ifft_basecase!(
9,
sd_ifft_basecase_9_0,
sd_ifft_basecase_9_1,
sd_ifft_basecase_7_0,
sd_ifft_basecase_7_1
);
fn sd_ifft_base_8_1(q: &FFTContext, x: &mut [f64], j: usize) {
set_j_bits_and_j_mr!(j, j_bits, j_mr);
if j == 0 {
sd_ifft_basecase_8_1(q, x);
} else {
sd_ifft_basecase_8_0(q, x, j_mr, j_bits);
}
}
fn sd_ifft_base_8_0(q: &FFTContext, x: &mut [f64], j: usize) {
set_j_bits_and_j_mr!(j, j_bits, j_mr);
sd_ifft_basecase_8_0(q, x, j_mr, j_bits);
}
fn sd_ifft_base_9_1(q: &FFTContext, x: &mut [f64], j: usize) {
set_j_bits_and_j_mr!(j, j_bits, j_mr);
if j == 0 {
sd_ifft_basecase_9_1(q, x);
} else {
sd_ifft_basecase_9_0(q, x, j_mr, j_bits);
}
}
fn sd_ifft_no_trunc_internal(
q: &FFTContext,
xs: &mut [f64],
s: usize,
k: u64,
j: usize,
) {
if k > 2 {
let k1 = k >> 1;
let k2 = k - k1;
let big_s_2 = s << (k2 + LG_BLK_SZ);
let shifted = j << k1;
let mut m = 0;
for b in 0..usize::power_of_2(k1) {
sd_ifft_no_trunc_internal(q, &mut xs[m..], s, k2, shifted + b);
m += big_s_2;
}
let big_s = s << LG_BLK_SZ;
let shifted = s << k2;
let mut m = 0;
for _ in 0..usize::power_of_2(k2) {
sd_ifft_no_trunc_block(q, &mut xs[m..], shifted, k1, j);
m += big_s;
}
return;
}
if k == 2 {
let big_s = s << LG_BLK_SZ;
split_into_chunks_mut!(xs, big_s, [xs0, xs1, xs2], xs3);
let j_4 = j << 2;
sd_ifft_base_8_1(q, xs0, j_4);
sd_ifft_base_8_0(q, xs1, j_4 + 1);
sd_ifft_base_8_0(q, xs2, j_4 + 2);
sd_ifft_base_8_0(q, xs3, j_4 + 3);
sd_ifft_no_trunc_block(q, xs, s, 2, j);
} else if k == 1 {
sd_ifft_base_9_1(q, xs, j);
} else {
sd_ifft_base_8_1(q, xs, j);
}
}
const BIG_M: usize = 4;
fn radix_4_moth_inv_trunc_block_3_4_1(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1, xs2], xs3);
let pow = usize::power_of_2(j_bits);
let j_mr = pow - 1 - j;
let j_r = j & ((pow >> 1) - 1);
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let j_bits = usize::exact_from(j_bits);
let w = if j == 0 {
-1.0
} else {
w2tab!(q, 1 + j_bits, (j_mr << 1) + 1)
};
let w2 = if j == 0 {
-1.0
} else {
w2tab!(q, j_bits, j_mr)
};
let two_w = f64_reduce_pm1n_to_pmhn!(-2.0 * w, q.p);
let f0 = f64x4::splat(w2tab!(q, 0, 1)); let f1 = f64x4::splat(two_w); let f2 = f64x4::splat(2.0);
let f3 = f64x4::splat(w2); let rw = if j == 0 {
w2tab!(q, 0, 1)
} else {
w2tab!(q, 1 + j_bits, (j_r << 1) + 1)
};
let fr = f64x4::splat(rw); let fq = f64x4::splat(w2tab!(q, j_bits, j_r)); let fp_alt = f64x4_mulmod!(fr, fq, n, ninv);
let fp = f64x4_reduce_pm1n_to_pmhn!(fp_alt, n); for i in (0..BLK_SZ).step_by(BIG_M) {
let a = read_f64x4!(xs0, i);
let mut b = read_f64x4!(xs1, i);
let mut c = read_f64x4!(xs2, i);
let mut d = read_f64x4!(xs3, i);
let mut v = a - b;
let p = f64x4_mulmod!(d, fp, n, ninv);
let q = f64x4_mulmod!(d, fq, n, ninv);
let r = f64x4_mulmod!(d, fr, n, ninv);
c = f64x4_reduce_to_pm1n!(c, n, ninv);
let u = f64x4_reduce_to_pm1n!(a + b, n, ninv);
b = f64x4_mulmod!(v, f1, n, ninv);
v = f64x4_mulmod!(v, f0, n, ninv);
d = c - v;
c = f64x4_mul_add!(f2, c, -v);
write_f64x4!(xs0, i, c + u + p);
write_f64x4!(xs1, i, b - q);
write_f64x4!(xs2, i, f64x4_mulmod!(c - u, f3, n, ninv) - r);
write_f64x4!(xs3, i, d + p);
}
}
fn radix_4_moth_inv_trunc_block_3_4_0(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1, xs2], xs3);
let pow = usize::power_of_2(j_bits);
let j_mr = pow - 1 - j;
let j_r = j & ((pow >> 1) - 1);
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let j_bits = usize::exact_from(j_bits);
let jp1 = j_bits + 1;
let w = if j == 0 {
-1.0
} else {
w2tab!(q, jp1, (j_mr << 1) + 1)
};
let w2 = if j == 0 {
-1.0
} else {
w2tab!(q, j_bits, j_mr)
};
let two_w = f64_reduce_pm1n_to_pmhn!(-2.0 * w, q.p);
let f0 = f64x4::splat(w2tab!(q, 0, 1)); let f1 = f64x4::splat(two_w); let f2 = f64x4::splat(2.0);
let f3 = f64x4::splat(w2); let rw = if j == 0 {
w2tab!(q, 0, 1)
} else {
w2tab!(q, jp1, (j_r << 1) + 1)
};
let fr = f64x4::splat(rw); let fq = f64x4::splat(w2tab!(q, j_bits, j_r)); let fp_alt = f64x4_mulmod!(fr, fq, n, ninv);
let fp = f64x4_reduce_pm1n_to_pmhn!(fp_alt, n); for i in (0..BLK_SZ).step_by(BIG_M) {
let a = read_f64x4!(xs0, i);
let b = read_f64x4!(xs1, i);
let mut c = read_f64x4!(xs2, i);
let d = read_f64x4!(xs3, i);
let u = f64x4_reduce_to_pm1n!(a + b, n, ninv);
let v = a - b;
c = f64x4_mul_add!(
f2,
f64x4_reduce_to_pm1n!(c, n, ninv),
-f64x4_mulmod!(v, f0, n, ninv)
);
write_f64x4!(xs0, i, c + u + f64x4_mulmod!(d, fp, n, ninv));
write_f64x4!(
xs1,
i,
f64x4_mulmod!(v, f1, n, ninv) - f64x4_mulmod!(d, fq, n, ninv)
);
write_f64x4!(
xs2,
i,
f64x4_mulmod!(c - u, f3, n, ninv) - f64x4_mulmod!(d, fr, n, ninv)
);
}
}
fn radix_4_moth_inv_trunc_block_3_3_1(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1, xs2], xs3);
let j_mr = usize::power_of_2(j_bits) - 1 - j;
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let j_bits = usize::exact_from(j_bits);
let w = if j == 0 {
-1.0
} else {
w2tab!(q, 1 + j_bits, (j_mr << 1) + 1)
};
let w2 = if j == 0 {
-1.0
} else {
w2tab!(q, j_bits, j_mr)
};
let two_w = f64_reduce_pm1n_to_pmhn!(-2.0 * w, q.p);
let f0 = f64x8::splat(w2tab!(q, 0, 1)); let f1 = f64x8::splat(two_w); let f2 = f64x8::splat(2.0);
let f3 = f64x8::splat(w2); for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let b = read_f64x8!(xs1, i);
let mut c = read_f64x8!(xs2, i);
let mut v = a - b;
write_f64x8!(xs1, i, f64x8_mulmod!(v, f1, n, ninv));
c = f64x8_reduce_to_pm1n!(c, n, ninv);
v = f64x8_mulmod!(v, f0, n, ninv);
write_f64x8!(xs3, i, c - v);
let u = f64x8_reduce_to_pm1n!(a + b, n, ninv);
c = f64x8_mul_add!(-f2, c, v);
write_f64x8!(xs0, i, u - c);
write_f64x8!(xs2, i, f64x8_nmulmod!(u + c, f3, n, ninv));
}
}
fn radix_4_moth_inv_trunc_block_3_3_0(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1], xs2);
let j_mr = usize::power_of_2(j_bits) - 1 - j;
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let j_bits = usize::exact_from(j_bits);
let w = if j == 0 {
-1.0
} else {
w2tab!(q, 1 + j_bits, (j_mr << 1) + 1)
};
let w2 = if j == 0 {
-1.0
} else {
w2tab!(q, j_bits, j_mr)
};
let two_w = f64_reduce_pm1n_to_pmhn!(-2.0 * w, q.p);
let f0 = f64x8::splat(w2tab!(q, 0, 1)); let f1 = f64x8::splat(two_w); let f2 = f64x8::splat(2.0);
let f3 = f64x8::splat(w2); for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let b = read_f64x8!(xs1, i);
let mut c = read_f64x8!(xs2, i);
let v = a - b;
write_f64x8!(xs1, i, f64x8_mulmod!(v, f1, n, ninv));
c = f64x8_reduce_to_pm1n!(c, n, ninv);
let mut u = a + b;
u = f64x8_reduce_to_pm1n!(u, n, ninv);
c = f64x8_mul_add!(-f2, c, f64x8_mulmod!(v, f0, n, ninv));
write_f64x8!(xs0, i, u - c);
write_f64x8!(xs2, i, f64x8_nmulmod!(u + c, f3, n, ninv));
}
}
fn radix_4_moth_inv_trunc_block_2_4_1(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1, xs2], xs3);
let pow = usize::power_of_2(j_bits);
let j_mr = pow - 1 - j;
let j_r = j & ((pow >> 1) - 1);
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let j_bits = usize::exact_from(j_bits);
let jp1 = j_bits + 1;
let big_w = if j == 0 {
-1.0
} else {
w2tab!(q, jp1, (j_mr << 1) + 1)
};
let rw = if j == 0 {
w2tab!(q, 0, 1)
} else {
w2tab!(q, jp1, (j_r << 1) + 1)
};
let w = w2tab!(q, j_bits, j_r);
let two_w = f64_reduce_pm1n_to_pmhn!(-2.0 * big_w, q.p);
let rw3 = f64_mulmod!(w, rw, q.p, q.pinv);
let f0 = f64x4::splat(2.0);
let f1 = f64x4::splat(two_w); let f2 = f64x4::splat(fma!(-0.5f64, q.p, 0.5)); let f3 = f64x4::splat(w2tab!(q, 0, 1)); let f4 = f64x4::splat(w); let f5 = f64x4::splat(f64_reduce_pm1n_to_pmhn!(rw3, q.p)); for i in (0..BLK_SZ).step_by(BIG_M) {
let u = read_f64x4!(xs0, i);
let v = read_f64x4!(xs1, i);
let a = read_f64x4!(xs2, i);
let b = read_f64x4!(xs3, i);
let p = f64x4_mulmod!(a, f4, n, ninv);
let q = f64x4_mulmod!(b, f4, n, ninv);
let r = f64x4_mulmod!(b, f5, n, ninv);
let s = f64x4_reduce_to_pm1n!(u + v, n, ninv);
let t = u - v;
write_f64x4!(xs0, i, f64x4_mulmod!(s, f0, n, ninv) - p);
write_f64x4!(xs1, i, f64x4_mulmod!(t, f1, n, ninv) - q);
write_f64x4!(
xs2,
i,
f64x4_mulmod!((s + f64x4_mulmod!(t, f3, n, ninv)) - (p + r), f2, n, ninv)
);
}
}
fn radix_4_moth_inv_trunc_block_2_4_0(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1, xs2], xs3);
let pow = usize::power_of_2(j_bits);
let j_mr = pow - 1 - j;
let j_r = j & ((pow >> 1) - 1);
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let j_bits = usize::exact_from(j_bits);
let wi = if j == 0 {
-1.0
} else {
w2tab!(q, 1 + j_bits, (j_mr << 1) + 1)
};
let w2 = f64x8::splat(w2tab!(q, j_bits, j_r));
let twowi = f64x8::splat(f64_reduce_pm1n_to_pmhn!(-2.0 * wi, q.p));
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let b = read_f64x8!(xs1, i);
let c = read_f64x8!(xs2, i);
let d = read_f64x8!(xs3, i);
let mut u = a + b;
u += u;
write_f64x8!(
xs0,
i,
f64x8_reduce_to_pm1n!(u, n, ninv) - f64x8_mulmod!(c, w2, n, ninv)
);
write_f64x8!(
xs1,
i,
f64x8_mulmod!(a - b, twowi, n, ninv) - f64x8_mulmod!(d, w2, n, ninv)
);
}
}
fn radix_4_moth_inv_trunc_block_2_2_1(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1], xs2);
let j_mr = usize::power_of_2(j_bits) - 1 - j;
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let j_bits = usize::exact_from(j_bits);
let w = if j == 0 {
-1.0
} else {
w2tab!(q, 1 + j_bits, (j_mr << 1) + 1)
};
let c1 = f64x8::splat(f64_reduce_pm1n_to_pmhn!(-2.0 * w, q.p)); let c2 = f64x8::splat(fma!(-0.5f64, q.p, 0.5)); let c3 = f64x8::splat(w2tab!(q, 1, 0)); for i in (0..BLK_SZ).step_by(BIG_N) {
let mut u = read_f64x8!(xs0, i);
let v = read_f64x8!(xs1, i);
let s = u + v;
let t = u - v;
u = s + s;
write_f64x8!(xs0, i, f64x8_reduce_to_pm1n!(u, n, ninv));
write_f64x8!(xs1, i, f64x8_mulmod!(t, c1, n, ninv));
write_f64x8!(
xs2,
i,
f64x8_mulmod!(s + f64x8_mulmod!(t, c3, n, ninv), c2, n, ninv)
);
}
}
fn radix_4_moth_inv_trunc_block_2_2_0(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
fail_on_untested_path("radix_4_moth_inv_trunc_block_2_2_0");
let (xs0, xs1) = xs.split_at_mut(gap);
let j_mr = usize::power_of_2(j_bits) - 1 - j;
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let j_bits = usize::exact_from(j_bits);
let w = if j == 0 {
-1.0
} else {
w2tab!(q, 1 + j_bits, (j_mr << 1) + 1)
};
let c0 = f64x8::splat(2.0);
let c1 = f64x8::splat(f64_reduce_pm1n_to_pmhn!(-2.0 * w, q.p));
for i in (0..BLK_SZ).step_by(BIG_N) {
let u = read_f64x8!(xs0, i);
let v = read_f64x8!(xs1, i);
write_f64x8!(xs0, i, f64x8_mulmod!(u + v, c0, n, ninv));
write_f64x8!(xs1, i, f64x8_mulmod!(u - v, c1, n, ninv));
}
}
fn radix_4_moth_inv_trunc_block_1_4_1(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1, xs2], xs3);
let j_r = j & ((usize::power_of_2(j_bits) >> 1) - 1);
let j_bits = usize::exact_from(j_bits);
let big_w2 = w2tab!(q, j_bits, j_r);
let w = if j == 0 {
1.0
} else {
w2tab!(q, 1 + j_bits, (j_r << 1))
};
let n = f64x4::splat(q.p);
let ninv = f64x4::splat(q.pinv);
let f2 = f64x4::splat(2.0);
let w2 = f64x4::splat(big_w2);
let ha = fma!(-0.5f64, q.p, 0.5);
let ha_w = f64_mulmod!(w, ha, q.p, q.pinv);
let wo2 = f64x4::splat(f64_reduce_pm1n_to_pmhn!(ha_w, q.p));
for i in (0..BLK_SZ).step_by(BIG_M) {
let mut a = read_f64x4!(xs0, i);
a = f64x4_reduce_to_pm1n!(a, n, ninv);
let mut b = read_f64x4!(xs1, i);
let mut c = read_f64x4!(xs2, i);
let mut d = read_f64x4!(xs3, i);
c = f64x4_nmulmod!(c, w2, n, ninv);
d = f64x4_mulmod!(d, w2, n, ninv);
b = f64x4_mulmod!(b + d, wo2, n, ninv);
let u = f64x4_mul_add!(-f2, a, b);
write_f64x4!(xs0, i, c - f64x4_reduce_to_pm1n!(u + u, n, ninv));
write_f64x4!(xs1, i, a - b);
}
}
fn radix_4_moth_inv_trunc_block_1_4_0(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1, xs2], xs3);
let j_r = j & ((usize::power_of_2(j_bits) >> 1) - 1);
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let f1 = f64x8::splat(4.0);
let j_bits = usize::exact_from(j_bits);
let w2 = f64x8::splat(w2tab!(q, j_bits, j_r));
let w = f64x8::splat(if j == 0 {
1.0
} else {
w2tab!(q, 1 + j_bits, j_r << 1)
});
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let b = read_f64x8!(xs1, i);
let c = read_f64x8!(xs2, i);
let d = read_f64x8!(xs3, i);
write_f64x8!(
xs0,
i,
f64x8_reduce_to_pm1n!(a * f1, n, ninv)
- f64x8_mulmod!(b, w, n, ninv)
- f64x8_mulmod!(c + f64x8_mulmod!(d, w, n, ninv), w2, n, ninv)
);
}
}
fn radix_4_moth_inv_trunc_block_1_1_1(
q: &FFTContext,
_j: usize,
_j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
fail_on_untested_path("radix_4_moth_inv_trunc_block_1_1_1");
let (xs0, xs1) = xs.split_at_mut(gap);
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let f = f64x8::splat(4.0);
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
write_f64x8!(xs0, i, f64x8_reduce_to_pm1n!(f * a, n, ninv));
write_f64x8!(xs1, i, f64x8_reduce_to_pm1n!(a, n, ninv));
}
}
fn radix_4_moth_inv_trunc_block_1_1_0(
q: &FFTContext,
_j: usize,
_j_bits: u64,
xs: &mut [f64],
_gap: usize,
) {
fail_on_untested_path("radix_4_moth_inv_trunc_block_1_1_0");
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let f = f64x8::splat(4.0);
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs, i);
write_f64x8!(xs, i, f64x8_reduce_to_pm1n!(f * a, n, ninv));
}
}
fn radix_4_moth_inv_trunc_block_0_4_1(
q: &FFTContext,
j: usize,
j_bits: u64,
xs: &mut [f64],
gap: usize,
) {
split_into_chunks_mut!(xs, gap, [xs0, xs1, xs2], xs3);
let j_r = j & ((usize::power_of_2(j_bits) >> 1) - 1);
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let one4th = f64x8::splat(fma!(-0.25f64, q.p, 0.25));
let j_bits = usize::exact_from(j_bits);
let w2 = f64x8::splat(w2tab!(q, j_bits, j_r));
let w = f64x8::splat(if j == 0 {
1.0
} else {
w2tab!(q, 1 + j_bits, j_r << 1)
});
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let b = read_f64x8!(xs1, i);
let c = read_f64x8!(xs2, i);
let d = read_f64x8!(xs3, i);
write_f64x8!(
xs0,
i,
f64x8_mulmod!(
a + f64x8_mulmod!(b, w, n, ninv)
+ f64x8_mulmod!(c + f64x8_mulmod!(d, w, n, ninv), w2, n, ninv),
one4th,
n,
ninv
)
);
}
}
fn sd_fft_ctx_w2(q: &FFTContext, j: usize) -> f64 {
set_j_bits_and_j_r!(j, j_bits, j_r);
w2tab!(q, usize::exact_from(j_bits), j_r)
}
fn radix_2_moth_inv_trunc_block_1_2_1(q: &FFTContext, j: usize, xs0: &mut [f64], xs1: &mut [f64]) {
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let w = f64x8::splat(sd_fft_ctx_w2(q, j));
let c = f64x8::splat(2.0);
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let mut b = read_f64x8!(xs1, i);
b = f64x8_nmulmod!(b, w, n, ninv);
write_f64x8!(
xs0,
i,
f64x8_reduce_to_pm1n!(f64x8_mul_add!(c, a, b), n, ninv)
);
write_f64x8!(xs1, i, f64x8_reduce_to_pm1n!(a + b, n, ninv));
}
}
fn radix_2_moth_inv_trunc_block_1_2_0(q: &FFTContext, j: usize, xs0: &mut [f64], xs1: &mut [f64]) {
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let w = f64x8::splat(sd_fft_ctx_w2(q, j));
let c = f64x8::splat(2.0);
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let b = read_f64x8!(xs1, i);
write_f64x8!(
xs0,
i,
f64x8_reduce_to_pm1n!(f64x8_mul_add!(c, a, -f64x8_mulmod!(b, w, n, ninv)), n, ninv)
);
}
}
fn radix_2_moth_inv_trunc_block_1_1_1(q: &FFTContext, _j: usize, xs0: &mut [f64], xs1: &mut [f64]) {
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let u = a + a;
write_f64x8!(xs0, i, f64x8_reduce_to_pm1n!(u, n, ninv));
write_f64x8!(xs1, i, a);
}
}
fn radix_2_moth_inv_trunc_block_1_1_0(
q: &FFTContext,
_j: usize,
xs0: &mut [f64],
_xs1: &mut [f64],
) {
fail_on_untested_path("radix_2_moth_inv_trunc_block_1_1_0");
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
for i in (0..BLK_SZ).step_by(BIG_N) {
let mut a = read_f64x8!(xs0, i);
a = a + a;
write_f64x8!(xs0, i, f64x8_reduce_to_pm1n!(a, n, ninv));
}
}
fn radix_2_moth_inv_trunc_block_0_2_1(q: &FFTContext, j: usize, xs0: &mut [f64], xs1: &mut [f64]) {
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let w = f64x8::splat(sd_fft_ctx_w2(q, j));
let c = f64x8::splat(fma!(-0.5f64, q.p, 0.5));
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
let b = read_f64x8!(xs1, i);
write_f64x8!(
xs0,
i,
f64x8_mulmod!(a + f64x8_mulmod!(b, w, n, ninv), c, n, ninv)
);
}
}
fn radix_2_moth_inv_trunc_block_0_1_1(
q: &FFTContext,
_j: usize,
xs0: &mut [f64],
_xs1: &mut [f64],
) {
fail_on_untested_path("radix_2_moth_inv_trunc_block_0_1_1");
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
let c = f64x8::splat(fma!(-0.5f64, q.p, 0.5));
for i in (0..BLK_SZ).step_by(BIG_N) {
let a = read_f64x8!(xs0, i);
write_f64x8!(xs0, i, f64x8_mulmod!(a, c, n, ninv));
}
}
const SD_IFFT_4_MOTH_TRUNC_BLOCK_TABLE: [Option<Sd4MothTruncBlockFn>; 40] = [
None,
None,
None,
None,
None,
None,
None,
Some(radix_4_moth_inv_trunc_block_0_4_1),
Some(radix_4_moth_inv_trunc_block_1_1_0),
Some(radix_4_moth_inv_trunc_block_1_1_1),
None,
None,
None,
None,
Some(radix_4_moth_inv_trunc_block_1_4_0),
Some(radix_4_moth_inv_trunc_block_1_4_1),
None,
None,
Some(radix_4_moth_inv_trunc_block_2_2_0),
Some(radix_4_moth_inv_trunc_block_2_2_1),
None,
None,
Some(radix_4_moth_inv_trunc_block_2_4_0),
Some(radix_4_moth_inv_trunc_block_2_4_1),
None,
None,
None,
None,
Some(radix_4_moth_inv_trunc_block_3_3_0),
Some(radix_4_moth_inv_trunc_block_3_3_1),
Some(radix_4_moth_inv_trunc_block_3_4_0),
Some(radix_4_moth_inv_trunc_block_3_4_1),
None,
None,
None,
None,
None,
None,
None,
None,
];
const SD_IFFT_2_MOTH_TRUNC_BLOCK_TABLE: [Option<Sd2MothTruncBlockFn>; 12] = [
None,
Some(radix_2_moth_inv_trunc_block_0_1_1),
None,
Some(radix_2_moth_inv_trunc_block_0_2_1),
Some(radix_2_moth_inv_trunc_block_1_1_0),
Some(radix_2_moth_inv_trunc_block_1_1_1),
Some(radix_2_moth_inv_trunc_block_1_2_0),
Some(radix_2_moth_inv_trunc_block_1_2_1),
None,
None,
None,
None,
];
fn sd_ifft_trunc_block(
q: &FFTContext,
xs: &mut [f64],
s: usize,
k: u64,
j: usize,
z: usize,
n: usize,
f: bool,
) {
assert!(n <= z);
let pow = usize::power_of_2(k);
assert!(1 <= z && z <= pow);
let sum = n + usize::from(f);
assert!(1 <= sum && sum <= pow);
if !f && z == n && n == pow {
sd_ifft_no_trunc_block(q, xs, s, k, j);
return;
}
let big_s = s << LG_BLK_SZ;
if k == 2
&& let Some(fxn) =
SD_IFFT_4_MOTH_TRUNC_BLOCK_TABLE[usize::from(f) + ((z - 1 + (n << 2)) << 1)]
{
fxn(q, j, j.significant_bits(), xs, big_s);
return;
}
if k > 1 {
let k1 = k >> 1;
let k2 = k - k1;
let l2 = usize::power_of_2(k2);
let n1 = n >> k2;
let mask = l2 - 1;
let n2 = n & mask;
let z1 = z >> k2;
let z2 = z & mask;
let fp = f || n2 != 0;
let z2p = min(l2, z);
let m = min(n2, z2);
let mp = max(n2, z2);
let big_s_2 = s << (k2 + LG_BLK_SZ);
let shifted = j << k1;
let mut p = 0;
for b in 0..n1 {
sd_ifft_no_trunc_block(q, &mut xs[p..], s, k2, shifted + b);
p += big_s_2;
}
let shifted2 = s << k2;
let mut p = n2 * big_s;
for a in n2..z2p {
sd_ifft_trunc_block(
q,
&mut xs[p..],
shifted2,
k1,
j,
z1 + usize::from(a < mp),
n1,
fp,
);
p += big_s;
}
if fp {
sd_ifft_trunc_block(q, &mut xs[n1 * big_s_2..], s, k2, shifted + n1, z2p, n2, f);
}
let sum = n1 + 1;
let mut p = 0;
for a in 0..n2 {
sd_ifft_trunc_block(
q,
&mut xs[p..],
shifted2,
k1,
j,
z1 + usize::from(a < m),
sum,
false,
);
p += big_s;
}
return;
}
if k == 1
&& let Some(fxn) =
SD_IFFT_2_MOTH_TRUNC_BLOCK_TABLE[usize::from(f) + ((z - 1 + (n << 1)) << 1)]
{
let (xs0, xs1) = xs.split_at_mut(big_s);
fxn(q, j, xs0, xs1);
}
}
fn sd_ifft_trunc_internal(
q: &FFTContext,
xs: &mut [f64],
s: usize,
k: u64,
j: usize,
z: usize,
n: usize,
f: bool,
) {
assert!(n <= z);
let big_z = z << LG_BLK_SZ;
let pow = usize::power_of_2(k + LG_BLK_SZ);
assert!(1 <= big_z && big_z <= pow);
let uf = usize::from(f);
let big_n = n << LG_BLK_SZ;
assert!(1 <= big_n + uf && big_n + uf <= pow);
if !f && z == n && n == usize::power_of_2(k) {
sd_ifft_no_trunc_internal(q, xs, s, k, j);
return;
}
let big_s = s << LG_BLK_SZ;
if k > 2 {
let k1 = k >> 1;
let k2 = k - k1;
let l2 = usize::power_of_2(k2);
let n1 = n >> k2;
let mask = l2 - 1;
let n2 = n & mask;
let z1 = z >> k2;
let z2 = z & mask;
let fp = f || n2 != 0;
let z2p = min(l2, z);
let m = min(n2, z2);
let mp = max(n2, z2);
let shifted = j << k1;
let big_s_2 = s << (k2 + LG_BLK_SZ);
let mut p = 0;
for b in 0..n1 {
sd_ifft_no_trunc_internal(q, &mut xs[p..], s, k2, shifted + b);
p += big_s_2;
}
let shifted_2 = s << k2;
let mut p = n2 * big_s;
for a in n2..z2p {
sd_ifft_trunc_block(
q,
&mut xs[p..],
shifted_2,
k1,
j,
z1 + usize::from(a < mp),
n1,
fp,
);
p += big_s;
}
if fp {
sd_ifft_trunc_internal(q, &mut xs[n1 * big_s_2..], s, k2, shifted + n1, z2p, n2, f);
}
let sum = n1 + 1;
let mut p = 0;
for a in 0..n2 {
sd_ifft_trunc_block(
q,
&mut xs[p..],
shifted_2,
k1,
j,
z1 + usize::from(a < m),
sum,
false,
);
p += big_s;
}
return;
}
if k == 2 {
let four_j = j << 2;
sd_ifft_base_8_1(q, xs, four_j);
if n > 1 {
sd_ifft_base_8_0(q, &mut xs[big_s..], four_j + 1);
}
if n > 2 {
sd_ifft_base_8_0(q, &mut xs[big_s << 1..], four_j + 2);
}
if n > 3 {
sd_ifft_base_8_0(q, &mut xs[big_s * 3..], four_j + 3);
}
sd_ifft_trunc_block(q, xs, s, 2, j, z, n, f);
if f {
sd_ifft_trunc_internal(q, &mut xs[big_s * n..], s, 0, four_j + n, 1, 0, f);
}
} else if k == 1 {
fail_on_untested_path("sd_ifft_trunc_internal, k == 1");
let two_j = j << 1;
sd_ifft_base_8_1(q, xs, two_j);
if n > 1 {
sd_ifft_base_8_0(q, &mut xs[big_s..], two_j + 1);
}
sd_ifft_trunc_block(q, xs, s, 1, j, z, n, f);
if f {
sd_ifft_trunc_internal(q, &mut xs[big_s * n..], s, 0, two_j + n, 1, 0, f);
}
} else {
assert!(!f);
sd_ifft_base_8_1(q, xs, j);
}
}
fn sd_ifft_trunc(
q: &mut FFTContext,
d: &mut [f64],
l: u64,
trunc: usize,
) {
assert!(trunc <= usize::power_of_2(l));
if l > LG_BLK_SZ {
let new_trunc = trunc.div_round(BLK_SZ, Ceiling).0;
sd_fft_ctx_fit_depth(q, l);
sd_ifft_trunc_internal(q, d, 1, l - LG_BLK_SZ, 0, new_trunc, new_trunc, false);
return;
}
fail_on_untested_path("sd_ifft_trunc, l <= LG_BLK_SZ");
match l {
0 => sd_ifft_basecase_0_1(q, d),
1 => sd_ifft_basecase_1_1(q, d),
2 => sd_ifft_basecase_2_1(q, d),
3 => sd_ifft_basecase_3_1(q, d),
4 => sd_ifft_basecase_4_1(q, d),
5 => sd_ifft_basecase_5_1(q, d),
6 => sd_ifft_basecase_6_1(q, d),
7 => sd_ifft_basecase_7_1(q, d),
8 => sd_ifft_basecase_8_1(q, d),
_ => unreachable!(),
}
}
fn sd_fft_ctx_point_mul(q: &FFTContext, a: &mut [f64], b: &[f64], m_orig: u64, depth: u64) {
let m = f64x8::splat(f64_reduce_0n_to_pmhn!(m_orig as f64, q.p));
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
assert!(depth >= LG_BLK_SZ);
let mut k = 0;
for _ in 0..usize::power_of_2(depth - LG_BLK_SZ) {
let ax = &mut a[k..];
let bx = &b[k..];
for j in (0..BLK_SZ).step_by(16) {
let mut x0 = read_f64x8!(ax, j);
let mut x1 = read_f64x8!(ax, j + 8);
let b0 = read_f64x8!(bx, j);
let b1 = read_f64x8!(bx, j + 8);
x0 = f64x8_mulmod!(x0, m, n, ninv);
x1 = f64x8_mulmod!(x1, m, n, ninv);
x0 = f64x8_mulmod!(x0, b0, n, ninv);
x1 = f64x8_mulmod!(x1, b1, n, ninv);
write_f64x8!(ax, j, x0);
write_f64x8!(ax, j + 8, x1);
}
k += BLK_SZ;
}
}
fn sd_fft_ctx_point_sqr(q: &FFTContext, a: &mut [f64], m_orig: u64, depth: u64) {
let m = f64x8::splat(f64_reduce_0n_to_pmhn!(m_orig as f64, q.p));
let n = f64x8::splat(q.p);
let ninv = f64x8::splat(q.pinv);
assert!(depth >= LG_BLK_SZ);
let mut k = 0;
for _ in 0..usize::power_of_2(depth - LG_BLK_SZ) {
let ax = &mut a[k..];
for j in (0..BLK_SZ).step_by(16) {
let mut x0 = read_f64x8!(ax, j);
let mut x1 = read_f64x8!(ax, j + 8);
x0 = f64x8_mulmod!(x0, x0, n, ninv);
x1 = f64x8_mulmod!(x1, x1, n, ninv);
x0 = f64x8_mulmod!(x0, m, n, ninv);
x1 = f64x8_mulmod!(x1, m, n, ninv);
write_f64x8!(ax, j, x0);
write_f64x8!(ax, j + 8, x1);
}
k += BLK_SZ;
}
}
fn flint_mpn_cmp_ui_2exp(a: &[u64], b: u64, e: u64) -> Ordering {
let mut an = a.len();
let mut q = usize::exact_from(e >> u64::LOG_WIDTH);
let r = e & u64::WIDTH_MASK;
while an != 0 && a[an - 1] == 0 {
an -= 1;
}
if an == 0 {
return if b == 0 { Equal } else { Greater };
}
let (b0, b1) = if r == 0 {
(b, 0)
} else {
(b << r, b >> (u64::WIDTH - r))
};
if an > q + 2 {
return Greater;
}
let mut x = if q + 1 < an { a[q + 1] } else { 0 };
if x != b1 {
return x.cmp(&b1);
}
x = if q < an { a[q] } else { 0 };
if x != b0 {
return x.cmp(&b0);
}
q = min(q, an);
if a[..q].iter().any(|&x| x != 0) {
return Greater;
}
Less
}
fn process_mod(
to_ffts: MPNToFFTFunc,
ffts: &[FFTContext],
stride: usize,
two_pow_tab: &[f64x4],
abuf: &mut [f64],
a: &[Limb],
atrunc: usize,
a_stop_easy: usize,
a_start_hard: usize,
a_stop_hard: usize,
bbuf: &mut [f64],
b: &[Limb],
btrunc: usize,
b_stop_easy: usize,
b_start_hard: usize,
b_stop_hard: usize,
squaring: bool,
) {
let f = match to_ffts {
MPNToFFTFunc { np: 4, bits: 84 } => apply_mpn_to_fft_func_4_84,
MPNToFFTFunc { np: 4, bits: 88 } => apply_mpn_to_fft_func_4_88,
MPNToFFTFunc { np: 4, bits: 92 } => apply_mpn_to_fft_func_4_92,
MPNToFFTFunc { np: 5, bits: 112 } => apply_mpn_to_fft_func_5_112,
MPNToFFTFunc { np: 5, bits: 116 } => apply_mpn_to_fft_func_5_116,
MPNToFFTFunc { np: 5, bits: 120 } => apply_mpn_to_fft_func_5_120,
MPNToFFTFunc { np: 6, bits: 136 } => apply_mpn_to_fft_func_6_136,
MPNToFFTFunc { np: 6, bits: 140 } => apply_mpn_to_fft_func_6_140,
MPNToFFTFunc { np: 6, bits: 144 } => apply_mpn_to_fft_func_6_144,
MPNToFFTFunc { np: 7, bits: 160 } => apply_mpn_to_fft_func_7_160,
MPNToFFTFunc { np: 7, bits: 164 } => apply_mpn_to_fft_func_7_164,
MPNToFFTFunc { np: 7, bits: 168 } => apply_mpn_to_fft_func_7_168,
MPNToFFTFunc { np: 8, bits: 184 } => apply_mpn_to_fft_func_8_184,
MPNToFFTFunc { np: 8, bits: 188 } => apply_mpn_to_fft_func_8_188,
MPNToFFTFunc { np: 8, bits: 192 } => apply_mpn_to_fft_func_8_192,
_ => unreachable!(),
};
f(
ffts,
abuf,
stride,
a,
atrunc,
two_pow_tab,
a_stop_easy,
a_start_hard,
a_stop_hard,
);
if !squaring {
f(
ffts,
bbuf,
stride,
b,
btrunc,
two_pow_tab,
b_stop_easy,
b_start_hard,
b_stop_hard,
);
}
}
macro_rules! nmod_red2 {
($a_hi: expr, $a_lo: expr, $mod_data: expr) => {{
let a_lo = $a_lo;
let mod_data = $mod_data;
let u1xx = ($a_hi << mod_data.norm)
+ (if mod_data.norm == 0 {
0
} else {
a_lo >> (u64::WIDTH - mod_data.norm)
});
let u0xx = a_lo << mod_data.norm;
let nxx = mod_data.n << mod_data.norm;
let (mut q1xx, mut q0xx) = u64::x_mul_y_to_zz(mod_data.ninv, u1xx);
(q1xx, q0xx) = u64::xx_add_yy_to_zz(q1xx, q0xx, u1xx, u0xx);
let mut r1xx = u0xx.wrapping_sub((q1xx + 1).wrapping_mul(nxx));
if r1xx > q0xx {
r1xx.wrapping_add_assign(nxx);
}
if r1xx < nxx {
r1xx >> mod_data.norm
} else {
(r1xx - nxx) >> mod_data.norm
}
}};
}
fn process_fft(
fctx: &mut FFTContext,
cop: u64,
depth: u64,
ztrunc: usize,
abuf: &mut [f64],
atrunc: usize,
bbuf: &mut [f64],
btrunc: usize,
squaring: bool,
) {
let q = &mut *fctx;
if !squaring {
sd_fft_trunc(q, bbuf, depth, btrunc, ztrunc);
}
sd_fft_trunc(q, abuf, depth, atrunc, ztrunc);
let m = nmod_red2!(cop >> (u64::WIDTH - depth), cop << depth, &q.mod_data)
.mod_inverse(q.mod_data.n)
.unwrap();
if squaring {
sd_fft_ctx_point_sqr(q, abuf, m, depth);
} else {
sd_fft_ctx_point_mul(q, abuf, bbuf, m, depth);
}
sd_ifft_trunc(q, abuf, depth, ztrunc);
}
fn process_mod_fft(
bits: usize,
fctx: &mut FFTContext,
two_pow_tab: &[f64],
cop: u64,
depth: u64,
ztrunc: usize,
a: &[Limb],
abuf: &mut [f64],
atrunc: usize,
b: &[Limb],
bbuf: &mut [f64],
btrunc: usize,
squaring: bool,
) {
let q = fctx;
if !squaring {
slow_mpn_to_fft(q, bbuf, btrunc, b, bits, two_pow_tab);
sd_fft_trunc(q, bbuf, depth, btrunc, ztrunc);
}
slow_mpn_to_fft(q, abuf, atrunc, a, bits, two_pow_tab);
sd_fft_trunc(q, abuf, depth, atrunc, ztrunc);
let m = nmod_red2!(cop >> (u64::WIDTH - depth), cop << depth, &q.mod_data)
.mod_inverse(q.mod_data.n)
.unwrap();
if squaring {
sd_fft_ctx_point_sqr(q, abuf, m, depth);
} else {
sd_fft_ctx_point_mul(q, abuf, bbuf, m, depth);
}
sd_ifft_trunc(q, abuf, depth, ztrunc);
}
macro_rules! madd {
($hi: expr, $lo: expr, $y: expr, $x: expr) => {
let (r1, r0) = u64::x_mul_y_to_zz($x, $y);
($hi, $lo) = u64::xx_add_yy_to_zz(r1, r0, $hi, $lo);
};
}
macro_rules! big_mul {
($n: expr, $m: expr, $r: ident, $t: ident, $c: expr, $y: expr) => {
let y = $y;
let c = $c;
for k in (0..$n).step_by(2) {
if k < const { $n - 1 } {
assert!(k < $m);
($r[k + 1], $r[k]) = u64::x_mul_y_to_zz(c[k], y);
} else {
assert_eq!(k, const { $n - 1 });
$r[k] = if k < $m { c[k] * y } else { 0 };
}
if k < const { $n - 2 } {
assert!(k < const { $m - 1 });
let kp1 = k + 1;
($t[k + 2], $t[kp1]) = u64::x_mul_y_to_zz(c[kp1], y);
} else if k < const { $n - 1 } {
let kp1 = k + 1;
$t[kp1] = if k < const { $m - 1 } { c[kp1] * y } else { 0 }
}
}
};
}
macro_rules! big_add_mul {
($n: expr, $m: expr, $r: ident, $t: ident, $c: expr, $y: expr) => {
let y = $y;
let c = $c;
for k in (0..$n).step_by(2) {
if k < const { $n - 1 } {
assert!(k < $m);
madd!($r[k + 1], $r[k], c[k], y);
} else {
assert_eq!(k, const { $n - 1 });
if k < $m {
$r[k] += c[k] * y;
}
}
#[allow(clippy::redundant_comparisons)]
if k < const { $n - 2 } {
assert!(k < const { $m - 1 });
let kp1 = k + 1;
madd!($t[k + 2], $t[kp1], c[kp1], y);
} else if k < const { $n - 1 } && k < const { $m - 1 } {
let kp1 = k + 1;
$t[kp1] += c[kp1] * y;
}
}
};
}
macro_rules! multi_add_3{($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
(z[2], z[1], z[0]) = u64::xxx_add_yyy_to_zzz(z[2], z[1], z[0], a[2], a[1], a[0]);
}}}
macro_rules! multi_add_4{($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
(z[3], z[2], z[1], z[0]) =
u64::xxxx_add_yyyy_to_zzzz(z[3], z[2], z[1], z[0], a[3], a[2], a[1], a[0]);
}}}
macro_rules! multi_add_5 {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let carry_1 = z[0].overflowing_add_assign(a[0]);
let mut carry_2 = z[1].overflowing_add_assign(a[1]);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a[2]);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a[3]);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
z[4].wrapping_add_assign(a[4]);
if carry_4 {
z[4].wrapping_add_assign(1);
}
}};
}
macro_rules! multi_add_6 {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let carry_1 = z[0].overflowing_add_assign(a[0]);
let mut carry_2 = z[1].overflowing_add_assign(a[1]);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a[2]);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a[3]);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
let mut carry_5 = z[4].overflowing_add_assign(a[4]);
if carry_4 {
carry_5 |= z[4].overflowing_add_assign(1);
}
z[5].wrapping_add_assign(a[5]);
if carry_5 {
z[5].wrapping_add_assign(1);
}
}};
}
#[cfg(not(feature = "32_bit_limbs"))]
macro_rules! multi_add_7 {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let carry_1 = z[0].overflowing_add_assign(a[0]);
let mut carry_2 = z[1].overflowing_add_assign(a[1]);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a[2]);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a[3]);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
let mut carry_5 = z[4].overflowing_add_assign(a[4]);
if carry_4 {
carry_5 |= z[4].overflowing_add_assign(1);
}
let mut carry_6 = z[5].overflowing_add_assign(a[5]);
if carry_5 {
carry_6 |= z[5].overflowing_add_assign(1);
}
z[6].wrapping_add_assign(a[6]);
if carry_6 {
z[6].wrapping_add_assign(1);
}
}};
}
#[cfg(not(feature = "32_bit_limbs"))]
macro_rules! multi_add_8 {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let carry_1 = z[0].overflowing_add_assign(a[0]);
let mut carry_2 = z[1].overflowing_add_assign(a[1]);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a[2]);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a[3]);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
let mut carry_5 = z[4].overflowing_add_assign(a[4]);
if carry_4 {
carry_5 |= z[4].overflowing_add_assign(1);
}
let mut carry_6 = z[5].overflowing_add_assign(a[5]);
if carry_5 {
carry_6 |= z[5].overflowing_add_assign(1);
}
let mut carry_7 = z[6].overflowing_add_assign(a[6]);
if carry_6 {
carry_7 |= z[6].overflowing_add_assign(1);
}
z[7].wrapping_add_assign(a[7]);
if carry_7 {
z[7].wrapping_add_assign(1);
}
}};
}
#[cfg(feature = "32_bit_limbs")]
macro_rules! multi_add_4_alt {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let (a_1, a_0) = a[0].split_in_half();
let (a_3, a_2) = a[1].split_in_half();
let (a_5, a_4) = a[2].split_in_half();
let (a_7, a_6) = a[3].split_in_half();
let carry_1 = z[0].overflowing_add_assign(a_0);
let mut carry_2 = z[1].overflowing_add_assign(a_1);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a_2);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a_3);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
let mut carry_5 = z[4].overflowing_add_assign(a_4);
if carry_4 {
carry_5 |= z[4].overflowing_add_assign(1);
}
let mut carry_6 = z[5].overflowing_add_assign(a_5);
if carry_5 {
carry_6 |= z[5].overflowing_add_assign(1);
}
let mut carry_7 = z[6].overflowing_add_assign(a_6);
if carry_6 {
carry_7 |= z[6].overflowing_add_assign(1);
}
if 7 < z.len() {
z[7].wrapping_add_assign(a_7);
if carry_7 {
z[7].wrapping_add_assign(1);
}
}
}};
}
#[cfg(feature = "32_bit_limbs")]
macro_rules! multi_add_5_alt {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let (a_1, a_0) = a[0].split_in_half();
let (a_3, a_2) = a[1].split_in_half();
let (a_5, a_4) = a[2].split_in_half();
let (a_7, a_6) = a[3].split_in_half();
let (a_9, a_8) = a[4].split_in_half();
let carry_1 = z[0].overflowing_add_assign(a_0);
let mut carry_2 = z[1].overflowing_add_assign(a_1);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a_2);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a_3);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
let mut carry_5 = z[4].overflowing_add_assign(a_4);
if carry_4 {
carry_5 |= z[4].overflowing_add_assign(1);
}
let mut carry_6 = z[5].overflowing_add_assign(a_5);
if carry_5 {
carry_6 |= z[5].overflowing_add_assign(1);
}
let mut carry_7 = z[6].overflowing_add_assign(a_6);
if carry_6 {
carry_7 |= z[6].overflowing_add_assign(1);
}
let mut carry_8 = z[7].overflowing_add_assign(a_7);
if carry_7 {
carry_8 |= z[7].overflowing_add_assign(1);
}
let mut carry_9 = z[8].overflowing_add_assign(a_8);
if carry_8 {
carry_9 |= z[8].overflowing_add_assign(1);
}
if 9 < z.len() {
z[9].wrapping_add_assign(a_9);
if carry_9 {
z[9].wrapping_add_assign(1);
}
}
}};
}
#[cfg(feature = "32_bit_limbs")]
macro_rules! multi_add_6_alt {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let (a_1, a_0) = a[0].split_in_half();
let (a_3, a_2) = a[1].split_in_half();
let (a_5, a_4) = a[2].split_in_half();
let (a_7, a_6) = a[3].split_in_half();
let (a_9, a_8) = a[4].split_in_half();
let (a_11, a_10) = a[5].split_in_half();
let carry_1 = z[0].overflowing_add_assign(a_0);
let mut carry_2 = z[1].overflowing_add_assign(a_1);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a_2);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a_3);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
let mut carry_5 = z[4].overflowing_add_assign(a_4);
if carry_4 {
carry_5 |= z[4].overflowing_add_assign(1);
}
let mut carry_6 = z[5].overflowing_add_assign(a_5);
if carry_5 {
carry_6 |= z[5].overflowing_add_assign(1);
}
let mut carry_7 = z[6].overflowing_add_assign(a_6);
if carry_6 {
carry_7 |= z[6].overflowing_add_assign(1);
}
let mut carry_8 = z[7].overflowing_add_assign(a_7);
if carry_7 {
carry_8 |= z[7].overflowing_add_assign(1);
}
let mut carry_9 = z[8].overflowing_add_assign(a_8);
if carry_8 {
carry_9 |= z[8].overflowing_add_assign(1);
}
let mut carry_10 = z[9].overflowing_add_assign(a_9);
if carry_9 {
carry_10 |= z[9].overflowing_add_assign(1);
}
let mut carry_11 = z[10].overflowing_add_assign(a_10);
if carry_10 {
carry_11 |= z[10].overflowing_add_assign(1);
}
if 11 < z.len() {
z[11].wrapping_add_assign(a_11);
if carry_11 {
z[11].wrapping_add_assign(1);
}
}
}};
}
#[cfg(feature = "32_bit_limbs")]
macro_rules! multi_add_7_alt {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let (a_1, a_0) = a[0].split_in_half();
let (a_3, a_2) = a[1].split_in_half();
let (a_5, a_4) = a[2].split_in_half();
let (a_7, a_6) = a[3].split_in_half();
let (a_9, a_8) = a[4].split_in_half();
let (a_11, a_10) = a[5].split_in_half();
let (a_13, a_12) = a[6].split_in_half();
let carry_1 = z[0].overflowing_add_assign(a_0);
let mut carry_2 = z[1].overflowing_add_assign(a_1);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a_2);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a_3);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
let mut carry_5 = z[4].overflowing_add_assign(a_4);
if carry_4 {
carry_5 |= z[4].overflowing_add_assign(1);
}
let mut carry_6 = z[5].overflowing_add_assign(a_5);
if carry_5 {
carry_6 |= z[5].overflowing_add_assign(1);
}
let mut carry_7 = z[6].overflowing_add_assign(a_6);
if carry_6 {
carry_7 |= z[6].overflowing_add_assign(1);
}
let mut carry_8 = z[7].overflowing_add_assign(a_7);
if carry_7 {
carry_8 |= z[7].overflowing_add_assign(1);
}
let mut carry_9 = z[8].overflowing_add_assign(a_8);
if carry_8 {
carry_9 |= z[8].overflowing_add_assign(1);
}
let mut carry_10 = z[9].overflowing_add_assign(a_9);
if carry_9 {
carry_10 |= z[9].overflowing_add_assign(1);
}
let mut carry_11 = z[10].overflowing_add_assign(a_10);
if carry_10 {
carry_11 |= z[10].overflowing_add_assign(1);
}
let mut carry_12 = z[11].overflowing_add_assign(a_11);
if carry_11 {
carry_12 |= z[11].overflowing_add_assign(1);
}
let mut carry_13 = z[12].overflowing_add_assign(a_12);
if carry_12 {
carry_13 |= z[12].overflowing_add_assign(1);
}
if 13 < z.len() {
z[13].wrapping_add_assign(a_13);
if carry_13 {
z[13].wrapping_add_assign(1);
}
}
}};
}
#[cfg(feature = "32_bit_limbs")]
macro_rules! multi_add_8_alt {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let (a_1, a_0) = a[0].split_in_half();
let (a_3, a_2) = a[1].split_in_half();
let (a_5, a_4) = a[2].split_in_half();
let (a_7, a_6) = a[3].split_in_half();
let (a_9, a_8) = a[4].split_in_half();
let (a_11, a_10) = a[5].split_in_half();
let (a_13, a_12) = a[6].split_in_half();
let (a_15, a_14) = a[7].split_in_half();
let carry_1 = z[0].overflowing_add_assign(a_0);
let mut carry_2 = z[1].overflowing_add_assign(a_1);
if carry_1 {
carry_2 |= z[1].overflowing_add_assign(1);
}
let mut carry_3 = z[2].overflowing_add_assign(a_2);
if carry_2 {
carry_3 |= z[2].overflowing_add_assign(1);
}
let mut carry_4 = z[3].overflowing_add_assign(a_3);
if carry_3 {
carry_4 |= z[3].overflowing_add_assign(1);
}
let mut carry_5 = z[4].overflowing_add_assign(a_4);
if carry_4 {
carry_5 |= z[4].overflowing_add_assign(1);
}
let mut carry_6 = z[5].overflowing_add_assign(a_5);
if carry_5 {
carry_6 |= z[5].overflowing_add_assign(1);
}
let mut carry_7 = z[6].overflowing_add_assign(a_6);
if carry_6 {
carry_7 |= z[6].overflowing_add_assign(1);
}
let mut carry_8 = z[7].overflowing_add_assign(a_7);
if carry_7 {
carry_8 |= z[7].overflowing_add_assign(1);
}
let mut carry_9 = z[8].overflowing_add_assign(a_8);
if carry_8 {
carry_9 |= z[8].overflowing_add_assign(1);
}
let mut carry_10 = z[9].overflowing_add_assign(a_9);
if carry_9 {
carry_10 |= z[9].overflowing_add_assign(1);
}
let mut carry_11 = z[10].overflowing_add_assign(a_10);
if carry_10 {
carry_11 |= z[10].overflowing_add_assign(1);
}
let mut carry_12 = z[11].overflowing_add_assign(a_11);
if carry_11 {
carry_12 |= z[11].overflowing_add_assign(1);
}
let mut carry_13 = z[12].overflowing_add_assign(a_12);
if carry_12 {
carry_13 |= z[12].overflowing_add_assign(1);
}
let mut carry_14 = z[13].overflowing_add_assign(a_13);
if carry_13 {
carry_14 |= z[13].overflowing_add_assign(1);
}
let mut carry_15 = z[14].overflowing_add_assign(a_14);
if carry_14 {
carry_15 |= z[14].overflowing_add_assign(1);
}
if 15 < z.len() {
z[15].wrapping_add_assign(a_15);
if carry_15 {
z[15].wrapping_add_assign(1);
}
}
}};
}
macro_rules! multi_sub_4 {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let borrow_1 = z[0].overflowing_sub_assign(a[0]);
let mut borrow_2 = z[1].overflowing_sub_assign(a[1]);
if borrow_1 {
borrow_2 |= z[1].overflowing_sub_assign(1);
}
let mut borrow_3 = z[2].overflowing_sub_assign(a[2]);
if borrow_2 {
borrow_3 |= z[2].overflowing_sub_assign(1);
}
z[3].wrapping_sub_assign(a[3]);
if borrow_3 {
z[3].wrapping_sub_assign(1);
}
}};
}
macro_rules! multi_sub_5 {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let borrow_1 = z[0].overflowing_sub_assign(a[0]);
let mut borrow_2 = z[1].overflowing_sub_assign(a[1]);
if borrow_1 {
borrow_2 |= z[1].overflowing_sub_assign(1);
}
let mut borrow_3 = z[2].overflowing_sub_assign(a[2]);
if borrow_2 {
borrow_3 |= z[2].overflowing_sub_assign(1);
}
let mut borrow_4 = z[3].overflowing_sub_assign(a[3]);
if borrow_3 {
borrow_4 |= z[3].overflowing_sub_assign(1);
}
z[4].wrapping_sub_assign(a[4]);
if borrow_4 {
z[4].wrapping_sub_assign(1);
}
}};
}
macro_rules! multi_sub_6 {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let borrow_1 = z[0].overflowing_sub_assign(a[0]);
let mut borrow_2 = z[1].overflowing_sub_assign(a[1]);
if borrow_1 {
borrow_2 |= z[1].overflowing_sub_assign(1);
}
let mut borrow_3 = z[2].overflowing_sub_assign(a[2]);
if borrow_2 {
borrow_3 |= z[2].overflowing_sub_assign(1);
}
let mut borrow_4 = z[3].overflowing_sub_assign(a[3]);
if borrow_3 {
borrow_4 |= z[3].overflowing_sub_assign(1);
}
let mut borrow_5 = z[4].overflowing_sub_assign(a[4]);
if borrow_4 {
borrow_5 |= z[4].overflowing_sub_assign(1);
}
z[5].wrapping_sub_assign(a[5]);
if borrow_5 {
z[5].wrapping_sub_assign(1);
}
}};
}
macro_rules! multi_sub_7 {
($z: expr, $a: expr) => {{
let z = $z;
let a = $a;
let borrow_1 = z[0].overflowing_sub_assign(a[0]);
let mut borrow_2 = z[1].overflowing_sub_assign(a[1]);
if borrow_1 {
borrow_2 |= z[1].overflowing_sub_assign(1);
}
let mut borrow_3 = z[2].overflowing_sub_assign(a[2]);
if borrow_2 {
borrow_3 |= z[2].overflowing_sub_assign(1);
}
let mut borrow_4 = z[3].overflowing_sub_assign(a[3]);
if borrow_3 {
borrow_4 |= z[3].overflowing_sub_assign(1);
}
let mut borrow_5 = z[4].overflowing_sub_assign(a[4]);
if borrow_4 {
borrow_5 |= z[4].overflowing_sub_assign(1);
}
let mut borrow_6 = z[5].overflowing_sub_assign(a[5]);
if borrow_5 {
borrow_6 |= z[5].overflowing_sub_assign(1);
}
z[6].wrapping_sub_assign(a[6]);
if borrow_6 {
z[6].wrapping_sub_assign(1);
}
}};
}
macro_rules! reduce_big_sum {
($n: expr, $faddm1: ident, $fsub: ident, $r: ident, $t: ident, $limit: expr) => {
let limit = $limit;
$faddm1!(&mut $r[1..], &$t[1..]);
'outer: loop {
let mut goto_sub = false;
for k in (2..=$n).rev() {
let km1 = k - 1;
if $r[km1] > limit[km1] {
goto_sub = true;
break;
}
if $r[km1] < limit[km1] {
break 'outer;
}
}
if !goto_sub && $r[0] < limit[0] {
break 'outer;
}
$fsub!(&mut $r, limit);
}
};
}
macro_rules! add_to_answer_easy {
(
$n: expr,
$fadd: ident,
$fadd_alt: ident,
$faddp1: ident,
$fadd_altp1: ident,
$z: ident,
$r: ident,
$toff: ident,
$tshift: ident
) => {{
#[cfg(feature = "32_bit_limbs")]
let z_len = $z.len().shr_round(1, Ceiling).0;
#[cfg(not(feature = "32_bit_limbs"))]
let z_len = $z.len();
assert!(z_len > $toff);
if $tshift == 0 {
#[cfg(feature = "32_bit_limbs")]
$fadd_alt!(&mut $z[$toff << 1..], $r);
#[cfg(not(feature = "32_bit_limbs"))]
$fadd!(&mut $z[$toff..], $r);
} else {
let comp_shift = 64 - $tshift;
$r[$n] = $r[$n - 1] >> comp_shift;
for k in (2..=$n).rev() {
$r[k - 1] = ($r[k - 1] << $tshift) | ($r[k - 2] >> comp_shift);
}
$r[0] <<= $tshift;
#[cfg(feature = "32_bit_limbs")]
$fadd_altp1!(&mut $z[$toff << 1..], $r);
#[cfg(not(feature = "32_bit_limbs"))]
$faddp1!(&mut $z[$toff..], $r);
}
}};
}
#[cfg(feature = "32_bit_limbs")]
fn limbs_slice_add_same_length_in_place_left_alt(xs: &mut [u32], ys: &[u64]) -> bool {
let mut carry = 0;
let mut xi = xs.iter_mut();
for &y in ys {
let (y_hi, y_lo) = y.split_in_half();
let x = xi.next().unwrap();
(*x, carry) = add_with_carry_limb(*x, y_lo, carry);
if let Some(x) = xi.next() {
(*x, carry) = add_with_carry_limb(*x, y_hi, carry);
}
}
carry != 0
}
macro_rules! add_to_answer_hard {
(
$n: expr,
$fadd: ident,
$fadd_alt: ident,
$faddp1: ident,
$fadd_altp1: ident,
$z: ident,
$r: ident,
$toff: ident,
$tshift: ident
) => {{
#[cfg(feature = "32_bit_limbs")]
let z_len = $z.len().shr_round(1, Ceiling).0;
#[cfg(not(feature = "32_bit_limbs"))]
let z_len = $z.len();
assert!(z_len > $toff);
let mut do_add = true;
if $tshift == 0 {
if z_len - $toff >= const { $n as usize } {
#[cfg(feature = "32_bit_limbs")]
$fadd_alt!(&mut $z[$toff << 1..], $r);
#[cfg(not(feature = "32_bit_limbs"))]
$fadd!(&mut $z[$toff..], $r);
do_add = false;
}
} else {
let comp_shift = 64 - $tshift;
$r[$n] = $r[const { $n - 1 }] >> comp_shift;
for k in (2..=$n).rev() {
$r[k - 1] = ($r[k - 1] << $tshift) | ($r[k - 2] >> comp_shift);
}
$r[0] <<= $tshift;
if z_len - $toff > const { $n as usize } {
#[cfg(feature = "32_bit_limbs")]
$fadd_altp1!(&mut $z[$toff << 1..], $r);
#[cfg(not(feature = "32_bit_limbs"))]
$faddp1!(&mut $z[$toff..], $r);
do_add = false;
}
}
if do_add {
let diff = z_len - $toff;
assert!(diff <= const { $n as usize });
#[cfg(feature = "32_bit_limbs")]
limbs_slice_add_same_length_in_place_left_alt(&mut $z[$toff << 1..], &$r[..diff]);
#[cfg(not(feature = "32_bit_limbs"))]
limbs_slice_add_same_length_in_place_left(&mut $z[$toff..], &$r[..diff]);
}
}};
}
macro_rules! f64x4_to_u64x4 {
($x: ident) => {{
let [a, b, c, d] = $x.to_array();
u64x4::from([
round_even!(a) as u64,
round_even!(b) as u64,
round_even!(c) as u64,
round_even!(d) as u64,
])
}};
}
macro_rules! convert_block {
(
$xs: ident,
$rffts: ident,
$d: ident,
$dstride: ident,
$np: expr,
$i: expr
) => {
let d_hi = &$d[$i << LG_BLK_SZ..];
let mut m = 0;
for l in 0..$np {
let xs_hi = &mut $xs[l << LG_BLK_SZ..];
let ds = &d_hi[m..];
let p = f64x4::splat($rffts[l].p);
let pinv = f64x4::splat($rffts[l].pinv);
for j in (0..BLK_SZ).step_by(VEC_SZ << 2) {
let mut x0 = read_f64x4!(ds, j);
let mut x1 = read_f64x4!(ds, j + VEC_SZ);
let mut x2 = read_f64x4!(ds, j + const { 2 * VEC_SZ });
let mut x3 = read_f64x4!(ds, j + const { 3 * VEC_SZ });
x0 = f64x4_reduce_to_0n!(x0, p, pinv);
x1 = f64x4_reduce_to_0n!(x1, p, pinv);
x2 = f64x4_reduce_to_0n!(x2, p, pinv);
x3 = f64x4_reduce_to_0n!(x3, p, pinv);
let y0 = f64x4_to_u64x4!(x0);
let y1 = f64x4_to_u64x4!(x1);
let y2 = f64x4_to_u64x4!(x2);
let y3 = f64x4_to_u64x4!(x3);
write_f64x4!(xs_hi, j, y0);
write_f64x4!(xs_hi, j + VEC_SZ, y1);
write_f64x4!(xs_hi, j + const { 2 * VEC_SZ }, y2);
write_f64x4!(xs_hi, j + const { 3 * VEC_SZ }, y3);
}
m += $dstride;
}
};
}
macro_rules! process_crt {
(
$f: ident,
$np: expr,
$n: expr,
$m: expr,
$faddm1: ident,
$fadd: ident,
$faddp1: ident,
$fadd_alt: ident,
$faddp1_alt: ident,
$fsub: ident
) => {
fn $f(
zs: &mut [Limb],
zlen: usize,
rffts: &[FFTContext],
ds: &[f64],
dstride: usize,
rcrts: &mut [CRTData],
bits: usize,
stop_easy: usize,
) {
#[cfg(feature = "32_bit_limbs")]
let zn_stop = zs.len().shr_round(1, Ceiling).0;
#[cfg(not(feature = "32_bit_limbs"))]
let zn_stop = zs.len();
let rcrt = &mut rcrts[const {$np - 1}];
assert_eq!($n, rcrt.coeff_len);
if const{$n != $m} {
for l in 0..$np {
assert_eq!(rcrt.co_prime(l)[$m], 0);
}
}
zs.fill(0);
let mut xs = [0; $np << LG_BLK_SZ];
let mut r = [0; $n + 1];
let mut t = [0; $n + 1];
let mut k = 0;
for i in (0..stop_easy).step_by(BLK_SZ) {
convert_block!(xs, rffts, ds, dstride, $np, i >> LG_BLK_SZ);
for j in 0..BLK_SZ {
big_mul!($n, $m, r, t, rcrt.co_prime(0), xs[j]);
let xs_hi = &xs[j..];
let mut m = BLK_SZ;
for l in 1..$np {
big_add_mul!($n, $m, r, t, rcrt.co_prime(l), xs_hi[m]);
m += BLK_SZ;
}
reduce_big_sum!($n, $faddm1, $fsub, r, t, rcrt.prod_primes_ref());
let toff = k >> u64::LOG_WIDTH;
let tshift = k & const {u64::WIDTH_MASK as usize};
assert!(zn_stop > $n + toff);
add_to_answer_easy!(
$n,
$fadd,
$fadd_alt,
$faddp1,
$faddp1_alt,
zs,
r,
toff,
tshift
);
k += bits;
}
}
let mut j = stop_easy * bits;
for i in stop_easy..zlen {
let ds_hi = &ds[i..];
let mut xx = ds_hi[0];
let rfft = &rffts[0];
let mut x = f64_reduce_to_0n!(xx, rfft.p, rfft.pinv) as u64;
big_mul!($n, $m, r, t, rcrt.co_prime(0), x);
let mut m = dstride;
for l in 1..$np {
xx = ds_hi[m];
let rfft = &rffts[l];
x = f64_reduce_to_0n!(xx, rfft.p, rfft.pinv) as u64;
big_add_mul!($n, $m, r, t, rcrt.co_prime(l), x);
m += dstride;
}
reduce_big_sum!($n, $faddm1, $fsub, r, t, rcrt.prod_primes_ref());
let toff = j >> u64::LOG_WIDTH;
let tshift = j & const {u64::WIDTH_MASK as usize};
if toff >= zn_stop {
break;
}
add_to_answer_hard!(
$n,
$fadd,
$fadd_alt,
$faddp1,
$faddp1_alt,
zs,
r,
toff,
tshift
);
j += bits;
}
}
}
}
process_crt!(
process_crt_4_4_3,
4,
4,
3,
multi_add_3,
multi_add_4,
multi_add_5,
multi_add_4_alt,
multi_add_5_alt,
multi_sub_4
);
process_crt!(
process_crt_5_4_4,
5,
4,
4,
multi_add_3,
multi_add_4,
multi_add_5,
multi_add_4_alt,
multi_add_5_alt,
multi_sub_4
);
process_crt!(
process_crt_6_5_4,
6,
5,
4,
multi_add_4,
multi_add_5,
multi_add_6,
multi_add_5_alt,
multi_add_6_alt,
multi_sub_5
);
process_crt!(
process_crt_7_6_5,
7,
6,
5,
multi_add_5,
multi_add_6,
multi_add_7,
multi_add_6_alt,
multi_add_7_alt,
multi_sub_6
);
process_crt!(
process_crt_8_7_6,
8,
7,
6,
multi_add_6,
multi_add_7,
multi_add_8,
multi_add_7_alt,
multi_add_8_alt,
multi_sub_7
);
fn mpn_ctx_mpn_mul(r: &mut Context, z: &mut [Limb], a: &[Limb], b: &[Limb], test_slow: bool) {
let squaring = core::ptr::addr_eq(a as *const [Limb], b as *const [Limb]) && a.len() == b.len();
let an = a.len();
let bn = b.len();
#[cfg(feature = "32_bit_limbs")]
let an = an.shr_round(1, Ceiling).0;
#[cfg(feature = "32_bit_limbs")]
let bn = bn.shr_round(1, Ceiling).0;
let mut p = ProfileEntry::default();
mpn_ctx_best_profile(r, &mut p, an, bn);
let zn = an + bn;
let alen = usize::exact_from(
u64::exact_from(an << u64::LOG_WIDTH)
.div_round(p.bits, Ceiling)
.0,
);
let blen = usize::exact_from(
u64::exact_from(bn << u64::LOG_WIDTH)
.div_round(p.bits, Ceiling)
.0,
);
let zlen = alen + blen - 1;
let atrunc = alen.round_to_multiple(BLK_SZ, Ceiling).0;
let btrunc = blen.round_to_multiple(BLK_SZ, Ceiling).0;
let ztrunc = zlen.round_to_multiple(BLK_SZ, Ceiling).0;
let depth = max(LG_BLK_SZ, ztrunc.ceiling_log_base_2());
let stride = usize::power_of_2(depth)
.round_to_multiple_of_power_of_2(7, Ceiling)
.0;
assert_ne!(an, 0);
assert_ne!(bn, 0);
let coeff_len = CONTEXT.crts[p.np - 1].coeff_len;
assert_ne!(
flint_mpn_cmp_ui_2exp(
&r.crts[p.np - 1].prod_primes_ref()[..coeff_len],
blen as u64,
p.bits << 1,
),
Less
);
let abuf;
if !test_slow && let Some(to_ffts) = p.to_ffts {
let bits = p.bits as usize;
let an_64 = an << 6;
let bn_64 = bn << 6;
let mut a_stop_easy = min(atrunc, (an_64 - 33) / bits);
let a_stop_hard = min(atrunc, an_64.div_ceil(bits));
let mut b_stop_easy = min(btrunc, (bn_64 - 33) / bits);
let b_stop_hard = min(btrunc, bn_64.div_ceil(bits));
let rounding: usize = if bits & 7 == 0 {
4
} else if bits & 3 == 0 {
8
} else {
16
};
let prod = p.np * stride;
mpn_ctx_fit_buffer(r, prod << 4);
let bbuf;
(abuf, bbuf) = r.buffer.split_at_mut(prod);
assert!(bits.even());
a_stop_easy &= rounding.wrapping_neg();
b_stop_easy &= rounding.wrapping_neg();
process_mod(
to_ffts,
&r.ffts,
stride,
&r.vec_two_pow_tab_backing
[r.vec_two_pow_tab_offsets[p.np.div_round(VEC_SZ, Ceiling).0 - 1]..],
abuf,
a,
atrunc,
a_stop_easy.round_to_multiple(rounding, Ceiling).0,
a_stop_easy,
a_stop_hard,
bbuf,
b,
btrunc,
b_stop_easy.round_to_multiple(rounding, Ceiling).0,
b_stop_easy,
b_stop_hard,
squaring,
);
let crt = &r.crts[p.np - 1];
let mut m = 0;
for l in 0..p.np {
process_fft(
&mut r.ffts[l],
crt.co_prime_red(l),
depth,
ztrunc,
&mut abuf[m..],
atrunc,
&mut bbuf[m..],
btrunc,
squaring,
);
m += stride;
}
} else {
mpn_ctx_fit_buffer(r, ((p.np + 1) * stride) << 3);
let crt = &r.crts[p.np - 1];
let bbuf;
(abuf, bbuf) = r.buffer.split_at_mut(p.np * stride);
let mut m = 0;
for l in 0..p.np {
process_mod_fft(
p.bits as usize,
&mut r.ffts[l],
&r.slow_two_pow_backing[r.slow_two_pow_offsets[l]..],
crt.co_prime_red(l),
depth,
ztrunc,
a,
&mut abuf[m..],
atrunc,
b,
bbuf,
btrunc,
squaring,
);
m += stride;
}
}
let n = r.crts[p.np - 1].coeff_len;
let mut end_easy = ((if zn > n { zn - (n + 1) } else { 0 }) << 6) / (p.bits as usize);
assert!(n <= MPN_CTX_NCRTS);
end_easy &= BLK_SZ.wrapping_neg();
assert!(4 <= p.np && p.np <= 8);
let process_crt = match p.np {
4 => process_crt_4_4_3,
5 => process_crt_5_4_4,
6 => process_crt_6_5_4,
7 => process_crt_7_6_5,
8 => process_crt_8_7_6,
_ => unreachable!(),
};
process_crt(
z,
zlen,
&r.ffts,
abuf,
stride,
&mut r.crts,
p.bits as usize,
end_easy.round_to_multiple(BLK_SZ, Ceiling).0,
);
}
pub(crate) fn mpn_mul_default_mpn_ctx(r1: &mut [Limb], i1: &[Limb], i2: &[Limb], test_slow: bool) {
let mut context = CONTEXT.deserialize();
mpn_ctx_mpn_mul(&mut context, r1, i1, i2, test_slow);
}
pub(crate) fn mpn_square_default_mpn_ctx(r1: &mut [Limb], i1: &[Limb]) {
let mut context = CONTEXT.deserialize();
mpn_ctx_mpn_mul(&mut context, r1, i1, i1, false);
}