use super::kernels::{divexact_finish, mul_1};
use super::limbs::{
abs_diff, add_1, add_ext, add_n, cmp_n, mul, mul_itch, sqr, sqr_itch, sub_ext, sub_n,
};
use core::cmp::Ordering;
fn stride(k: usize) -> usize {
(k + 2) & !1
}
pub(super) fn toom33_fits(nx: usize, ny: usize) -> bool {
nx >= 12 && ny > 2 * nx.div_ceil(3)
}
pub(super) fn toom44_fits(nx: usize, ny: usize) -> bool {
nx >= 16 && ny > 3 * nx.div_ceil(4)
}
fn toom42_part(nx: usize, ny: usize) -> usize {
if nx >= 2 * ny {
nx.div_ceil(4)
} else {
ny.div_ceil(2)
}
}
pub(super) fn toom42_fits(nx: usize, ny: usize) -> bool {
let k = toom42_part(nx, ny);
k >= 4 && nx > 3 * k && nx <= 4 * k && ny > k && ny <= 2 * k
}
fn parts_itch(k: usize, sx: usize, ty: usize) -> usize {
mul_itch(k + 1, k + 1)
.max(mul_itch(k, k))
.max(mul_itch(sx, ty))
}
pub(super) fn toom33_itch(nx: usize, ny: usize) -> usize {
let k = nx.div_ceil(3);
4 * k + 4 + 3 * stride(k) + parts_itch(k, nx - 2 * k, ny - 2 * k)
}
pub(super) fn toom42_itch(nx: usize, ny: usize) -> usize {
let k = toom42_part(nx, ny);
let (sx, ty) = (nx - 3 * k, ny - k);
4 * k + 4 + 3 * stride(k) + parts_itch(k, sx.max(ty), sx.min(ty))
}
pub(super) fn toom3_sqr_itch(n: usize) -> usize {
let k = n.div_ceil(3);
4 * k + 4 + 2 * stride(k) + sqr_itch(k + 1).max(sqr_itch(k)).max(sqr_itch(n - 2 * k))
}
pub(super) fn toom44_itch(nx: usize, ny: usize) -> usize {
let k = nx.div_ceil(4);
8 * k + 8 + 2 * stride(k) + parts_itch(k, nx - 3 * k, ny - 3 * k)
}
pub(super) fn toom4_sqr_itch(n: usize) -> usize {
let k = n.div_ceil(4);
8 * k + 8 + 2 * stride(k) + sqr_itch(k + 1).max(sqr_itch(k)).max(sqr_itch(n - 3 * k))
}
pub(super) unsafe fn toom33(
r: *mut u64,
x: *const u64,
nx: usize,
y: *const u64,
ny: usize,
s: *mut u64,
) {
let k = nx.div_ceil(3);
let (sx, ty) = (nx - 2 * k, ny - 2 * k);
debug_assert!(0 < ty && ty <= sx && sx <= k);
unsafe {
let v = stride(k);
let (vm1, v2) = (s, s.add(2 * k + 2));
let (xp1, xm1, ym1) = (
s.add(4 * k + 4),
s.add(4 * k + 4 + v),
s.add(4 * k + 4 + 2 * v),
);
let rest = s.add(4 * k + 4 + 3 * v);
let (yp1, xp2, yp2) = (r, r.add(v), r.add(2 * v));
let negative_x = eval3(xp1, xm1, xp2, x, k, sx);
let negative_y = eval3(yp1, ym1, yp2, y, k, ty);
product_of_values(vm1, xm1, ym1, k, rest);
mul(v2, xp2, k + 1, yp2, k + 1, rest);
mul(r.add(2 * k), xp1, k + 1, yp1, k + 1, rest);
let v1_top = *r.add(4 * k);
mul(r.add(4 * k), x.add(2 * k), sx, y.add(2 * k), ty, rest);
mul(r, x, k, y, k, rest);
interpolate5(r, k, sx + ty, vm1, v2, v1_top, negative_x != negative_y);
}
}
pub(super) unsafe fn toom3_sqr(r: *mut u64, x: *const u64, n: usize, s: *mut u64) {
let k = n.div_ceil(3);
let sx = n - 2 * k;
debug_assert!(0 < sx && sx <= k);
unsafe {
let v = stride(k);
let (vm1, v2) = (s, s.add(2 * k + 2));
let (p1, m1) = (s.add(4 * k + 4), s.add(4 * k + 4 + v));
let rest = s.add(4 * k + 4 + 2 * v);
let p2 = r;
eval3(p1, m1, p2, x, k, sx);
if *m1.add(k) == 0 {
sqr(vm1, m1, k, rest);
*vm1.add(2 * k) = 0;
} else {
sqr(vm1, m1, k + 1, rest);
}
sqr(v2, p2, k + 1, rest);
sqr(r.add(2 * k), p1, k + 1, rest);
let v1_top = *r.add(4 * k);
sqr(r.add(4 * k), x.add(2 * k), sx, rest);
sqr(r, x, k, rest);
interpolate5(r, k, 2 * sx, vm1, v2, v1_top, false);
}
}
unsafe fn eval3(
p1: *mut u64,
m1: *mut u64,
p2: *mut u64,
x: *const u64,
k: usize,
s: usize,
) -> bool {
unsafe {
let (x0, x1, x2) = (x, x.add(k), x.add(2 * k));
*p2.add(k) = u64::from(add_ext(p2, x0, k, x2, s));
*p1.add(k) = *p2.add(k) + u64::from(add_n(p1, p2, x1, k));
let negative = *p2.add(k) == 0 && cmp_n(p2, x1, k) == Ordering::Less;
if negative {
sub_n(m1, x1, p2, k);
*m1.add(k) = 0;
} else {
*m1.add(k) = *p2.add(k) - u64::from(sub_n(m1, p2, x1, k));
}
*p2.add(k) = *p1.add(k) + u64::from(add_ext(p2, p1, k, x2, s));
lshift::<1>(p2, p2, k + 1);
*p2.add(k) -= u64::from(sub_n(p2, p2, x0, k));
negative
}
}
pub(super) unsafe fn toom42(
r: *mut u64,
x: *const u64,
nx: usize,
y: *const u64,
ny: usize,
s: *mut u64,
) {
let k = toom42_part(nx, ny);
let (sx, ty) = (nx - 3 * k, ny - k);
debug_assert!(0 < sx && sx <= k && 0 < ty && ty <= k);
unsafe {
let v = stride(k);
let (vm1, v2) = (s, s.add(2 * k + 2));
let (xp1, xm1, ym1) = (
s.add(4 * k + 4),
s.add(4 * k + 4 + v),
s.add(4 * k + 4 + 2 * v),
);
let rest = s.add(4 * k + 4 + 3 * v);
let (yp1, xp2, yp2) = (r, r.add(v), r.add(2 * v));
let negative_x = eval4_pm1(xp1, xm1, x, k, sx, v2);
eval4_pm2(xp2, v2, x, k, sx, v2.add(k + 1));
let (y0, y1) = (y, y.add(k));
*yp1.add(k) = u64::from(add_ext(yp1, y0, k, y1, ty));
let negative_y = abs_diff(ym1, y0, k, y1, ty);
*yp2.add(k) = *yp1.add(k) + u64::from(add_ext(yp2, yp1, k, y1, ty));
if *xm1.add(k) == 0 {
mul(vm1, xm1, k, ym1, k, rest);
*vm1.add(2 * k) = 0;
} else {
mul(vm1, xm1, k + 1, ym1, k, rest);
}
mul(v2, xp2, k + 1, yp2, k + 1, rest);
mul(r.add(2 * k), xp1, k + 1, yp1, k + 1, rest);
let v1_top = *r.add(4 * k);
let (x3, y1) = (x.add(3 * k), y.add(k));
if sx >= ty {
mul(r.add(4 * k), x3, sx, y1, ty, rest);
} else {
mul(r.add(4 * k), y1, ty, x3, sx, rest);
}
mul(r, x, k, y, k, rest);
interpolate5(r, k, sx + ty, vm1, v2, v1_top, negative_x != negative_y);
}
}
unsafe fn interpolate5(
r: *mut u64,
k: usize,
w: usize,
vm1: *mut u64,
v2: *mut u64,
v1_top: u64,
negative: bool,
) {
let m = 2 * k + 1;
let mut top = v1_top;
unsafe {
let v1 = r.add(2 * k);
let vinf = r.add(4 * k);
if negative {
add_n(v2, v2, vm1, m);
} else {
sub_n(v2, v2, vm1, m);
}
divexact::<3>(v2, m);
if negative {
let carry = add_n(vm1, v1, vm1, 2 * k);
*vm1.add(2 * k) += top + u64::from(carry);
} else {
let borrow = sub_n(vm1, v1, vm1, 2 * k);
*vm1.add(2 * k) = top - *vm1.add(2 * k) - u64::from(borrow);
}
rshift::<1>(vm1, vm1, m);
top -= u64::from(sub_n(v1, v1, r, 2 * k));
let borrow = sub_n(v2, v2, v1, 2 * k);
*v2.add(2 * k) = *v2.add(2 * k) - top - u64::from(borrow);
rshift::<1>(v2, v2, m);
let borrow = sub_n(v1, v1, vm1, 2 * k);
top = top - *vm1.add(2 * k) - u64::from(borrow);
sub_ext(v2, v2, m, vinf, w);
sub_ext(v2, v2, m, vinf, w);
top -= u64::from(sub_ext(v1, v1, 2 * k, vinf, w));
sub_n(vm1, vm1, v2, m);
let carry = add_n(r.add(k), r.add(k), vm1, m);
top += add_1(r.add(3 * k + 1), k - 1, u64::from(carry));
let carry = add_n(r.add(3 * k), r.add(3 * k), v2, k);
let out = add_1(v2.add(k), k + 1, top + u64::from(carry));
debug_assert!(out == 0);
if w > k + 1 {
let carry = add_n(vinf, vinf, v2.add(k), k + 1);
add_1(vinf.add(k + 1), w - k - 1, u64::from(carry));
} else {
add_n(vinf, vinf, v2.add(k), w);
}
}
}
pub(super) unsafe fn toom44(
r: *mut u64,
x: *const u64,
nx: usize,
y: *const u64,
ny: usize,
s: *mut u64,
) {
let k = nx.div_ceil(4);
let (sx, ty) = (nx - 3 * k, ny - 3 * k);
debug_assert!(0 < ty && ty <= sx && sx <= k);
unsafe {
let (n, v) = (2 * k + 2, stride(k));
let (w1, w3, w4, w5) = (s, s.add(n), s.add(2 * n), s.add(3 * n));
let (ya, g) = (s.add(4 * n), s.add(4 * n + v));
let rest = s.add(4 * n + 2 * v);
let (xa, xb, yb) = (r, r.add(v), r.add(2 * v));
let negative_x = eval4_pm2(xa, xb, x, k, sx, g);
let negative_y = eval4_pm2(ya, yb, y, k, ty, g);
mul(w4, xa, k + 1, ya, k + 1, rest);
mul(w1, xb, k + 1, yb, k + 1, rest);
let negative_2 = negative_x != negative_y;
eval4_half(xa, x, k, sx);
eval4_half(ya, y, k, ty);
mul(w5, xa, k + 1, ya, k + 1, rest);
let negative_x = eval4_pm1(xa, xb, x, k, sx, g);
let negative_y = eval4_pm1(ya, yb, y, k, ty, g);
product_of_values(w3, xb, yb, k, rest);
mul(r.add(2 * k), xa, k + 1, ya, k + 1, rest);
mul(r.add(6 * k), x.add(3 * k), sx, y.add(3 * k), ty, rest);
mul(r, x, k, y, k, rest);
interpolate7(
r,
k,
sx + ty,
[w1, w3, w4, w5],
negative_2,
negative_x != negative_y,
ya,
);
}
}
pub(super) unsafe fn toom4_sqr(r: *mut u64, x: *const u64, n: usize, s: *mut u64) {
let k = n.div_ceil(4);
let sx = n - 3 * k;
debug_assert!(0 < sx && sx <= k);
unsafe {
let (m, v) = (2 * k + 2, stride(k));
let (w1, w3, w4, w5) = (s, s.add(m), s.add(2 * m), s.add(3 * m));
let g = s.add(4 * m);
let rest = s.add(4 * m + 2 * v);
let (a, b) = (r, r.add(v));
eval4_pm2(a, b, x, k, sx, g);
sqr(w4, a, k + 1, rest);
sqr(w1, b, k + 1, rest);
eval4_half(a, x, k, sx);
sqr(w5, a, k + 1, rest);
eval4_pm1(a, b, x, k, sx, g);
if *b.add(k) == 0 {
sqr(w3, b, k, rest);
*w3.add(2 * k) = 0;
} else {
sqr(w3, b, k + 1, rest);
}
sqr(r.add(2 * k), a, k + 1, rest);
sqr(r.add(6 * k), x.add(3 * k), sx, rest);
sqr(r, x, k, rest);
interpolate7(r, k, 2 * sx, [w1, w3, w4, w5], false, false, g);
}
}
unsafe fn eval4_pm1(
p: *mut u64,
m: *mut u64,
x: *const u64,
k: usize,
s: usize,
g: *mut u64,
) -> bool {
unsafe {
*p.add(k) = u64::from(add_n(p, x, x.add(2 * k), k));
*g.add(k) = u64::from(add_ext(g, x.add(k), k, x.add(3 * k), s));
sum_and_difference(p, m, g, k + 1)
}
}
unsafe fn eval4_pm2(
p: *mut u64,
m: *mut u64,
x: *const u64,
k: usize,
s: usize,
g: *mut u64,
) -> bool {
unsafe {
*p.add(k) = lshift::<2>(p, x.add(2 * k), k);
*p.add(k) += u64::from(add_n(p, p, x, k));
*m.add(s) = lshift::<2>(m, x.add(3 * k), s);
*g.add(k) = if s < k {
u64::from(add_ext(g, x.add(k), k, m, s + 1))
} else {
*m.add(k) + u64::from(add_n(g, x.add(k), m, k))
};
lshift::<1>(g, g, k + 1);
sum_and_difference(p, m, g, k + 1)
}
}
unsafe fn eval4_half(p: *mut u64, x: *const u64, k: usize, s: usize) {
unsafe {
*p.add(k) = lshift::<1>(p, x, k);
*p.add(k) += u64::from(add_n(p, p, x.add(k), k));
lshift::<1>(p, p, k + 1);
*p.add(k) += u64::from(add_n(p, p, x.add(2 * k), k));
lshift::<1>(p, p, k + 1);
add_ext(p, p, k + 1, x.add(3 * k), s);
}
}
unsafe fn sum_and_difference(p: *mut u64, m: *mut u64, g: *const u64, n: usize) -> bool {
unsafe {
let negative = cmp_n(p, g, n) == Ordering::Less;
if negative {
sub_n(m, g, p, n);
} else {
sub_n(m, p, g, n);
}
add_n(p, p, g, n);
negative
}
}
unsafe fn interpolate7(
r: *mut u64,
k: usize,
w: usize,
ws: [*mut u64; 4],
negative_2: bool,
negative_1: bool,
t: *mut u64,
) {
let m = 2 * k + 1;
let [w1, w3, w4, w5] = ws;
unsafe {
let (w0, w2, w6) = (r, r.add(2 * k), r.add(6 * k));
add_n(w5, w5, w4, m);
if negative_2 {
add_n(w1, w4, w1, m);
} else {
sub_n(w1, w4, w1, m);
}
rshift::<1>(w1, w1, m);
sub_ext(w4, w4, m, w0, 2 * k);
sub_n(w4, w4, w1, m);
rshift::<2>(w4, w4, m);
*t.add(w) = lshift::<4>(t, w6, w);
sub_ext(w4, w4, m, t, w + 1);
if negative_1 {
add_n(w3, w2, w3, m);
} else {
sub_n(w3, w2, w3, m);
}
rshift::<1>(w3, w3, m);
sub_n(w2, w2, w3, m);
mul_1(t, w2, m, 65);
sub_n(w5, w5, t, m);
sub_ext(w2, w2, m, w6, w);
sub_ext(w2, w2, m, w0, 2 * k);
mul_1(t, w2, m, 45);
add_n(w5, w5, t, m);
rshift::<1>(w5, w5, m);
sub_n(w4, w4, w2, m);
divexact::<3>(w4, m);
sub_n(w2, w2, w4, m);
sub_n(w1, w5, w1, m);
lshift::<3>(t, w3, m);
sub_n(w5, w5, t, m);
divexact::<3>(w5, m);
divexact::<3>(w5, m);
sub_n(w3, w3, w5, m);
divexact::<15>(w1, m);
add_n(w1, w1, w5, m);
rshift::<1>(w1, w1, m);
sub_n(w5, w5, w1, m);
let carry = add_n(r.add(k), r.add(k), w1, m);
add_1(r.add(3 * k + 1), k, u64::from(carry));
let carry = add_n(r.add(3 * k), r.add(3 * k), w3, k);
add_1(w3.add(k), k + 1, *r.add(4 * k) + u64::from(carry));
let carry = add_n(r.add(4 * k), w3.add(k), w4, k);
add_1(w4.add(k), k + 1, *w3.add(2 * k) + u64::from(carry));
let carry = add_n(r.add(5 * k), w4.add(k), w5, k);
add_1(w5.add(k), k + 1, *w4.add(2 * k) + u64::from(carry));
if w > k + 1 {
let carry = add_n(w6, w6, w5.add(k), k + 1);
add_1(w6.add(k + 1), w - k - 1, u64::from(carry));
} else {
add_n(w6, w6, w5.add(k), w);
}
}
}
unsafe fn product_of_values(r: *mut u64, a: *const u64, b: *const u64, k: usize, s: *mut u64) {
unsafe {
if *a.add(k) | *b.add(k) == 0 {
mul(r, a, k, b, k, s);
*r.add(2 * k) = 0;
} else {
mul(r, a, k + 1, b, k + 1, s);
}
}
}
#[inline]
unsafe fn lshift<const C: u32>(r: *mut u64, a: *const u64, n: usize) -> u64 {
unsafe {
let out = *a.add(n - 1) >> (64 - C);
for i in (1..n).rev() {
*r.add(i) = (*a.add(i) << C) | (*a.add(i - 1) >> (64 - C));
}
*r = *a << C;
out
}
}
#[inline]
unsafe fn rshift<const C: u32>(r: *mut u64, a: *const u64, n: usize) {
unsafe {
debug_assert!(*a & ((1 << C) - 1) == 0, "an inexact halving");
for i in 0..n - 1 {
*r.add(i) = (*a.add(i) >> C) | (*a.add(i + 1) << (64 - C));
}
*r.add(n - 1) = *a.add(n - 1) >> C;
}
}
#[inline]
unsafe fn divexact<const D: u64>(r: *mut u64, n: usize) {
unsafe {
mul_1(r, r, n, u64::MAX / D);
divexact_finish(r, n);
}
}