#[cfg(target_endian = "little")]
pub(in super::super) use super::super::add::{add_limbs as add_n, sub_limbs as sub_n};
use super::fft::{self, FFT_MUL, FFT_MUL_UNBALANCED, FFT_SQR};
use super::kernels::{mul_basecase, sqr_basecase};
use super::toom;
pub(super) const KARATSUBA_MUL: usize = 24;
pub(super) const KARATSUBA_SQR: usize = 56;
pub(super) const TOOM3_MUL: usize = 120;
pub(super) const TOOM4_MUL: usize = 224;
pub(super) const TOOM3_SQR: usize = 168;
pub(super) const TOOM4_SQR: usize = 224;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Method {
Schoolbook,
Pieces,
Karatsuba,
Toom3,
Toom4,
Toom42,
Fft,
}
#[inline]
fn method(nx: usize, ny: usize) -> Method {
let fft = ny >= FFT_MUL || (ny >= FFT_MUL_UNBALANCED && nx >= 2 * ny);
if fft && fft::fits(nx, ny) {
Method::Fft
} else if ny < KARATSUBA_MUL {
Method::Schoolbook
} else if ny < TOOM3_MUL {
if ny <= nx.div_ceil(2) {
Method::Pieces
} else {
Method::Karatsuba
}
} else if ny >= TOOM4_MUL && toom::toom44_fits(nx, ny) {
Method::Toom4
} else if toom::toom33_fits(nx, ny) {
Method::Toom3
} else if toom::toom42_fits(nx, ny) {
Method::Toom42
} else if ny <= nx.div_ceil(2) {
Method::Pieces
} else {
Method::Karatsuba
}
}
#[inline]
fn sqr_method(n: usize) -> Method {
if n >= FFT_SQR && fft::fits(n, n) {
Method::Fft
} else if n < KARATSUBA_SQR {
Method::Schoolbook
} else if n < TOOM3_SQR {
Method::Karatsuba
} else if n < TOOM4_SQR {
Method::Toom3
} else {
Method::Toom4
}
}
pub(in super::super) fn mul_itch(nx: usize, ny: usize) -> usize {
match method(nx, ny) {
Method::Schoolbook => 0,
Method::Pieces => {
let (p, full, last) = pieces(nx, ny);
let itch = |len: usize| mul_itch(len.max(ny), len.min(ny));
let rest = if full > 0 { itch(p) } else { 0 }.max(itch(last));
let t = if full > 0 { p.max(last) } else { last };
itch(p).max(t + ny + rest)
}
Method::Karatsuba => {
let n = nx.div_ceil(2);
let (sx, ty) = (nx - n, ny - n);
2 * n + mul_itch(n, n).max(mul_itch(sx, ty))
}
Method::Toom3 => toom::toom33_itch(nx, ny),
Method::Toom4 => toom::toom44_itch(nx, ny),
Method::Toom42 => toom::toom42_itch(nx, ny),
Method::Fft => 0,
}
}
pub(in super::super) fn sqr_itch(n: usize) -> usize {
match sqr_method(n) {
Method::Schoolbook | Method::Pieces | Method::Toom42 | Method::Fft => 0,
Method::Karatsuba => {
let h = n.div_ceil(2);
2 * h + sqr_itch(h).max(sqr_itch(n - h))
}
Method::Toom3 => toom::toom3_sqr_itch(n),
Method::Toom4 => toom::toom4_sqr_itch(n),
}
}
pub(in super::super) unsafe fn mul(
r: *mut u64,
x: *const u64,
nx: usize,
y: *const u64,
ny: usize,
s: *mut u64,
) {
debug_assert!(nx >= ny && ny >= 1);
unsafe {
match method(nx, ny) {
Method::Schoolbook => mul_basecase(r, x, nx, y, ny),
Method::Pieces => mul_pieces(r, x, nx, y, ny, s),
Method::Karatsuba => karatsuba(r, x, nx, y, ny, s),
Method::Toom3 => toom::toom33(r, x, nx, y, ny, s),
Method::Toom4 => toom::toom44(r, x, nx, y, ny, s),
Method::Toom42 => toom::toom42(r, x, nx, y, ny, s),
Method::Fft => fft::mul(r, x, nx, y, ny),
}
}
}
pub(in super::super) unsafe fn sqr(r: *mut u64, x: *const u64, n: usize, s: *mut u64) {
debug_assert!(n >= 1);
unsafe {
match sqr_method(n) {
Method::Schoolbook | Method::Pieces | Method::Toom42 => sqr_basecase(r, x, n),
Method::Fft => fft::sqr(r, x, n),
Method::Karatsuba => karatsuba_sqr(r, x, n, s),
Method::Toom3 => toom::toom3_sqr(r, x, n, s),
Method::Toom4 => toom::toom4_sqr(r, x, n, s),
}
}
}
fn pieces(nx: usize, ny: usize) -> (usize, usize, usize) {
let half = ny / 2;
let p = if ny >= TOOM3_MUL && nx >= 2 * ny + half {
2 * ny
} else {
ny
};
let left = nx - p;
let full = if left >= p + half {
(left - half) / p
} else {
0
};
(p, full, left - full * p)
}
unsafe fn mul_any(r: *mut u64, x: *const u64, nx: usize, y: *const u64, ny: usize, s: *mut u64) {
unsafe {
if nx >= ny {
mul(r, x, nx, y, ny, s);
} else {
mul(r, y, ny, x, nx, s);
}
}
}
unsafe fn mul_pieces(r: *mut u64, x: *const u64, nx: usize, y: *const u64, ny: usize, s: *mut u64) {
let (p, full, last) = pieces(nx, ny);
unsafe {
mul_any(r, x, p, y, ny, s);
let longest = if full > 0 { p.max(last) } else { last };
let (t, rest) = (s, s.add(longest + ny));
let mut done = p;
for i in 0..=full {
let len = if i < full { p } else { last };
mul_any(t, x.add(done), len, y, ny, rest);
let carry = add_n(r.add(done), r.add(done), t, ny);
core::ptr::copy_nonoverlapping(t.add(ny), r.add(done + ny), len);
let out = add_1(r.add(done + ny), len, u64::from(carry));
debug_assert!(out == 0, "a product of pieces carried out of its limbs");
done += len;
}
debug_assert!(done == nx);
}
}
unsafe fn karatsuba(r: *mut u64, x: *const u64, nx: usize, y: *const u64, ny: usize, s: *mut u64) {
let n = nx.div_ceil(2);
let (sx, ty) = (nx - n, ny - n);
debug_assert!(0 < ty && ty <= sx && sx <= n);
unsafe {
let (x1, y1) = (x.add(n), y.add(n));
let negative_x = abs_diff(r, x, n, x1, sx);
let negative_y = abs_diff(r.add(n), y, n, y1, ty);
let rest = s.add(2 * n);
mul(s, r, n, r.add(n), n, rest);
mul(r.add(2 * n), x1, sx, y1, ty, rest);
mul(r, x, n, y, n, rest);
middle(r, n, nx + ny, s, negative_x == negative_y);
}
}
unsafe fn karatsuba_sqr(r: *mut u64, x: *const u64, n: usize, s: *mut u64) {
let h = n.div_ceil(2);
let sx = n - h;
unsafe {
let x1 = x.add(h);
abs_diff(r, x, h, x1, sx);
let rest = s.add(2 * h);
sqr(s, r, h, rest);
sqr(r.add(2 * h), x1, sx, rest);
sqr(r, x, h, rest);
middle(r, h, 2 * n, s, true);
}
}
unsafe fn middle(r: *mut u64, n: usize, len: usize, s: *mut u64, subtract: bool) {
unsafe {
let high = len - 3 * n;
let c = add_n(r.add(2 * n), r.add(n), r.add(2 * n), n);
let c2 = u64::from(c) + u64::from(add_n(r.add(n), r.add(2 * n), r, n));
let mut cy =
i64::from(c) + i64::from(add_ext(r.add(2 * n), r.add(2 * n), n, r.add(3 * n), high));
if subtract {
cy -= i64::from(sub_n(r.add(n), r.add(n), s, 2 * n));
} else {
cy += i64::from(add_n(r.add(n), r.add(n), s, 2 * n));
}
add_1(r.add(2 * n), len - 2 * n, c2);
if cy >= 0 {
add_1(r.add(3 * n), high, cy as u64);
} else {
sub_1(r.add(3 * n), high, 1);
}
}
}
pub(super) unsafe fn abs_diff(
r: *mut u64,
a: *const u64,
na: usize,
b: *const u64,
nb: usize,
) -> bool {
unsafe {
let above = (nb..na).any(|i| *a.add(i) != 0);
let less = !above && cmp_n(a, b, nb) == core::cmp::Ordering::Less;
if less {
sub_n(r, b, a, nb);
core::ptr::write_bytes(r.add(nb), 0, na - nb);
} else {
let borrow = sub_n(r, a, b, nb);
core::ptr::copy_nonoverlapping(a.add(nb), r.add(nb), na - nb);
let out = sub_1(r.add(nb), na - nb, u64::from(borrow));
debug_assert!(out == 0);
}
less
}
}
pub(in super::super) unsafe fn cmp_n(
a: *const u64,
b: *const u64,
n: usize,
) -> core::cmp::Ordering {
unsafe {
for i in (0..n).rev() {
let (x, y) = (*a.add(i), *b.add(i));
if x != y {
return x.cmp(&y);
}
}
}
core::cmp::Ordering::Equal
}
pub(super) unsafe fn add_ext(
r: *mut u64,
a: *const u64,
na: usize,
b: *const u64,
nb: usize,
) -> bool {
unsafe {
let carry = add_n(r, a, b, nb);
if r.cast_const() != a {
core::ptr::copy_nonoverlapping(a.add(nb), r.add(nb), na - nb);
}
add_1(r.add(nb), na - nb, u64::from(carry)) != 0
}
}
pub(super) unsafe fn sub_ext(
r: *mut u64,
a: *const u64,
na: usize,
b: *const u64,
nb: usize,
) -> bool {
unsafe {
let borrow = sub_n(r, a, b, nb);
if r.cast_const() != a {
core::ptr::copy_nonoverlapping(a.add(nb), r.add(nb), na - nb);
}
sub_1(r.add(nb), na - nb, u64::from(borrow)) != 0
}
}
pub(in super::super) unsafe fn add_1(r: *mut u64, n: usize, mut c: u64) -> u64 {
unsafe {
let mut i = 0;
while c != 0 && i < n {
let (v, o) = (*r.add(i)).overflowing_add(c);
*r.add(i) = v;
c = u64::from(o);
i += 1;
}
}
c
}
pub(in super::super) unsafe fn sub_1(r: *mut u64, n: usize, mut c: u64) -> u64 {
unsafe {
let mut i = 0;
while c != 0 && i < n {
let (v, o) = (*r.add(i)).overflowing_sub(c);
*r.add(i) = v;
c = u64::from(o);
i += 1;
}
}
c
}
#[cfg(target_endian = "big")]
pub(in super::super) unsafe fn add_n(r: *mut u64, a: *const u64, b: *const u64, n: usize) -> bool {
let mut carry = false;
unsafe {
for i in 0..n {
let (v, c1) = (*a.add(i)).overflowing_add(*b.add(i));
let (v, c2) = v.overflowing_add(u64::from(carry));
*r.add(i) = v;
carry = c1 | c2;
}
}
carry
}
#[cfg(target_endian = "big")]
pub(in super::super) unsafe fn sub_n(r: *mut u64, a: *const u64, b: *const u64, n: usize) -> bool {
let mut borrow = false;
unsafe {
for i in 0..n {
let (v, b1) = (*a.add(i)).overflowing_sub(*b.add(i));
let (v, b2) = v.overflowing_sub(u64::from(borrow));
*r.add(i) = v;
borrow = b1 | b2;
}
}
borrow
}