use super::super::mul::limbs::{add_n, cmp_n, mul};
use super::kernels::{addmul_1, extend_row, reduce_pair};
use super::limbs::{
Row, Step, StepExt, add_to, divexact, divexact_itch, gcdext_lehmer, hgcd2_hinted,
lehmer_ext_itch, mul_bound, normalized, sub_from, subdiv, subdiv_ext, tdiv_itch, tdiv_qr,
top_limbs, with_scratch,
};
use core::cmp::Ordering;
use core::ptr;
pub(super) const HGCD_THRESHOLD: usize = if cfg!(miri) { 8 } else { 250 };
pub(super) const GCD_DC_THRESHOLD: usize = if cfg!(miri) { 6 } else { 800 };
pub(super) const GCDEXT_DC_THRESHOLD: usize = if cfg!(miri) { 6 } else { 300 };
#[inline(always)]
unsafe fn mul_any(r: *mut u64, x: *const u64, xn: usize, y: *const u64, yn: usize, s: *mut u64) {
unsafe {
if xn >= yn {
mul(r, x, xn, y, yn, s);
} else {
mul(r, y, yn, x, xn, s);
}
}
}
fn mul_any_itch(xn: usize, yn: usize) -> usize {
mul_bound(xn, yn)
}
pub(super) struct Matrix {
pub(super) alloc: usize,
pub(super) n: usize,
pub(super) p: [[*mut u64; 2]; 2],
pub(super) ones: bool,
}
pub(super) fn matrix_itch(n: usize) -> usize {
4 * (n.div_ceil(2) + 1)
}
impl Matrix {
pub(super) unsafe fn new(n: usize, mem: *mut u64) -> Matrix {
let alloc = n.div_ceil(2) + 1;
unsafe {
ptr::write_bytes(mem, 0, 4 * alloc);
*mem = 1;
*mem.add(3 * alloc) = 1;
Matrix {
alloc,
n: 1,
ones: false,
p: [
[mem, mem.add(alloc)],
[mem.add(2 * alloc), mem.add(3 * alloc)],
],
}
}
}
unsafe fn update(&mut self, q: *const u64, qn: usize, col: usize, t: *mut u64) {
unsafe {
if qn == 1 {
let n = self.n;
let c0 = addmul_1(self.p[0][col], self.p[0][1 - col], n, *q);
let c1 = addmul_1(self.p[1][col], self.p[1][1 - col], n, *q);
*self.p[0][col].add(n) = c0;
*self.p[1][col].add(n) = c1;
self.n = n + usize::from(c0 | c1 != 0);
debug_assert!(self.n < self.alloc);
return;
}
let mut n = self.n;
while n + qn > self.n
&& *self.p[0][1 - col].add(n - 1) | *self.p[1][1 - col].add(n - 1) == 0
{
n -= 1;
}
debug_assert!(n + qn < self.alloc + 1);
let mut carry = [0u64; 2];
for (row, c) in carry.iter_mut().enumerate() {
mul_any(t, self.p[row][1 - col], n, q, qn, t.add(n + qn));
*c = add_to(self.p[row][col], t, n + qn, self.p[row][col], self.n);
}
let mut len = n + qn;
if carry[0] | carry[1] != 0 {
*self.p[0][col].add(len) = carry[0];
*self.p[1][col].add(len) = carry[1];
len += 1;
} else if *self.p[0][col].add(len - 1) | *self.p[1][col].add(len - 1) == 0 {
len -= 1;
}
for row in 0..2 {
for i in self.n..len {
*self.p[row][1 - col].add(i) = 0;
}
}
self.n = len.max(self.n);
debug_assert!(self.n < self.alloc);
}
}
unsafe fn mul_1(&mut self, m1: super::limbs::Matrix1) {
unsafe {
let n = self.n;
let mut grow = false;
for row in 0..2 {
let (c0, c1) = extend_row(self.p[row][0], self.p[row][1], n, m1);
*self.p[row][0].add(n) = c0;
*self.p[row][1].add(n) = c1;
grow |= c0 | c1 != 0;
}
self.n = n + usize::from(grow);
debug_assert!(self.n < self.alloc);
}
}
unsafe fn mul(&mut self, m1: &Matrix, t: *mut u64) {
let (rn, mn) = (self.n, m1.n);
let len = rn + mn;
debug_assert!(len < self.alloc);
unsafe {
let (x, y) = (t, t.add(len + 1));
let w = y.add(len);
for row in 0..2 {
let (r0, r1) = (self.p[row][0], self.p[row][1]);
mul_any(x, r0, rn, m1.p[0][0], mn, w);
mul_any(y, r1, rn, m1.p[1][0], mn, w);
*x.add(len) = u64::from(add_n(x, x, y, len));
mul_any(y, r0, rn, m1.p[0][1], mn, w);
mul_any(r0, r1, rn, m1.p[1][1], mn, w);
*r1.add(len) = u64::from(add_n(r1, r0, y, len));
ptr::copy_nonoverlapping(x, r0, len + 1);
}
let mut n = len + 1;
while n > 1
&& *self.p[0][0].add(n - 1)
| *self.p[0][1].add(n - 1)
| *self.p[1][0].add(n - 1)
| *self.p[1][1].add(n - 1)
== 0
{
n -= 1;
}
self.n = n;
}
}
pub(super) unsafe fn adjust(
&self,
n: usize,
a: *mut u64,
b: *mut u64,
p: usize,
t: *mut u64,
) -> usize {
let mn = self.n;
debug_assert!(p + mn < n);
unsafe {
let (t0, t1) = (t, t.add(p + mn));
let w = t1.add(p + mn);
mul_any(t0, self.p[1][1], mn, a, p, w);
mul_any(t1, self.p[1][0], mn, a, p, w);
ptr::copy_nonoverlapping(t0, a, p);
let mut ah = add_to(a.add(p), a.add(p), n - p, t0.add(p), mn);
mul_any(t0, self.p[0][1], mn, b, p, w);
let borrow = sub_from(a, a, n, t0, p + mn);
debug_assert!(borrow <= ah);
ah -= borrow;
mul_any(t0, self.p[0][0], mn, b, p, w);
ptr::copy_nonoverlapping(t0, b, p);
let mut bh = add_to(b.add(p), b.add(p), n - p, t0.add(p), mn);
let borrow = sub_from(b, b, n, t1, p + mn);
debug_assert!(borrow <= bh);
bh -= borrow;
if ah | bh != 0 {
*a.add(n) = ah;
*b.add(n) = bh;
n + 1
} else if *a.add(n - 1) | *b.add(n - 1) == 0 {
n - 1
} else {
n
}
}
}
}
fn update_itch(n: usize, qn: usize) -> usize {
if qn == 1 {
0
} else {
n + qn + mul_any_itch(n, qn)
}
}
fn mul_matrix_itch(rn: usize, mn: usize) -> usize {
2 * (rn + mn) + 1 + mul_any_itch(rn, mn)
}
pub(super) fn adjust_itch(p: usize, mn: usize) -> usize {
2 * (p + mn) + mul_any_itch(p, mn)
}
unsafe fn subdiv_hgcd(
a: *mut u64,
b: *mut u64,
n: usize,
s: usize,
m: &mut Matrix,
t: *mut u64,
avail: usize,
) -> usize {
unsafe {
let (mut pa, mut pb) = (a, b);
let mut an = normalized(pa, n);
let mut bn = normalized(pb, n);
let mut col = 0;
let swap = match an.cmp(&bn) {
Ordering::Equal => match cmp_n(pa, pb, an) {
Ordering::Equal => return 0,
order => order == Ordering::Greater,
},
order => order == Ordering::Greater,
};
if swap {
core::mem::swap(&mut pa, &mut pb);
core::mem::swap(&mut an, &mut bn);
col = 1;
}
if an <= s {
return 0;
}
let borrow = sub_from(pb, pb, bn, pa, an);
debug_assert!(borrow == 0);
bn = normalized(pb, bn);
if bn <= s {
let carry = add_to(pb, pa, an, pb, bn);
*pb.add(an) = carry;
return 0;
}
let order = if bn == an {
cmp_n(pb, pa, an)
} else {
bn.cmp(&an)
};
m.update(&1, 1, col, t);
if order == Ordering::Equal {
return an;
}
if order == Ordering::Less {
core::mem::swap(&mut pa, &mut pb);
core::mem::swap(&mut an, &mut bn);
col ^= 1;
}
let qn = bn - an + 1;
let (q, w) = (t, t.add(qn));
let room = avail.saturating_sub(qn);
with_scratch(w, room, tdiv_itch(bn, an), |w| {
tdiv_qr(q, pb, bn, pa, an, w)
});
let rn = normalized(pb, an);
let mut size = an;
if rn <= s {
let mut i = 0;
while *q.add(i) == 0 {
*q.add(i) = u64::MAX;
i += 1;
}
*q.add(i) -= 1;
let carry = add_to(pb, pa, an, pb, rn);
if carry != 0 {
*pb.add(an) = carry;
size += 1;
}
}
let qn = normalized(q, qn);
if qn > 0 {
with_scratch(w, room, update_itch(m.n, qn), |w| m.update(q, qn, col, w));
}
size
}
}
fn step_itch(n: usize) -> usize {
(n + 1) + tdiv_itch(n, 2).max(update_itch(n / 2 + 2, n))
}
unsafe fn hgcd_step(
n: usize,
a: *mut u64,
b: *mut u64,
s: usize,
m: &mut Matrix,
t: *mut u64,
avail: usize,
) -> usize {
unsafe {
let top = if n == s + 1 {
let mask = *a.add(n - 1) | *b.add(n - 1);
if mask < 4 {
None
} else {
Some((*a.add(n - 1), *a.add(n - 2), *b.add(n - 1), *b.add(n - 2)))
}
} else {
Some(top_limbs(a, b, n))
};
if let Some(m1) = top.and_then(|(ah, al, bh, bl)| hgcd2_hinted(ah, al, bh, bl, &mut m.ones))
{
m.mul_1(m1);
reduce_pair(a, b, n, m1);
return n - usize::from(*a.add(n - 1) | *b.add(n - 1) == 0);
}
subdiv_hgcd(a, b, n, s, m, t, avail)
}
}
pub(super) fn hgcd_itch(n: usize) -> usize {
if n < HGCD_THRESHOLD {
return step_itch(n);
}
let p = n / 2;
let s = n / 2 + 1;
let n2 = 3 * n / 4 + 1;
let half = (n - p).max(2 * n2 - 2 * s);
let mn = half.div_ceil(2) + 1;
let inner = hgcd_itch(half);
let first = inner.max(adjust_itch(p, mn));
let second = matrix_itch(half)
+ inner
.max(adjust_itch(n, mn))
.max(mul_matrix_itch(n.div_ceil(2) + 1, mn));
first.max(second).max(step_itch(n))
}
pub(super) unsafe fn hgcd(
a: *mut u64,
b: *mut u64,
mut n: usize,
m: &mut Matrix,
t: *mut u64,
) -> usize {
let s = n / 2 + 1;
if n <= s {
return 0;
}
let avail = hgcd_itch(n);
let mut success = false;
unsafe {
if n >= HGCD_THRESHOLD {
let n2 = 3 * n / 4 + 1;
let p = n / 2;
let nn = hgcd(a.add(p), b.add(p), n - p, m, t);
if nn > 0 {
n = m.adjust(p + nn, a, b, p, t);
success = true;
}
while n > n2 {
let nn = hgcd_step(n, a, b, s, m, t, avail);
if nn == 0 {
return if success { n } else { 0 };
}
n = nn;
success = true;
}
if n > s + 2 {
let p = 2 * s - n + 1;
let mm = matrix_itch(n - p);
let mut m1 = Matrix::new(n - p, t);
let nn = hgcd(a.add(p), b.add(p), n - p, &mut m1, t.add(mm));
if nn > 0 {
n = m1.adjust(p + nn, a, b, p, t.add(mm));
m.mul(&m1, t.add(mm));
success = true;
}
}
}
loop {
let nn = hgcd_step(n, a, b, s, m, t, avail);
if nn == 0 {
return if success { n } else { 0 };
}
n = nn;
success = true;
}
}
}
pub(super) fn gcd_dc_itch(n: usize) -> usize {
let p = 2 * n / 3;
let mn = (n - p).div_ceil(2) + 1;
matrix_itch(n - p) + hgcd_itch(n - p).max(adjust_itch(p, mn))
}
pub(super) unsafe fn gcd_dc(
a: &mut *mut u64,
b: &mut *mut u64,
mut n: usize,
t: *mut u64,
avail: usize,
) -> Step {
unsafe {
while n >= GCD_DC_THRESHOLD {
let p = 2 * n / 3;
let mut m = Matrix::new(n - p, t);
let w = t.add(matrix_itch(n - p));
let nn = hgcd(a.add(p), b.add(p), n - p, &mut m, w);
if nn > 0 {
n = m.adjust(p + nn, *a, *b, p, w);
} else {
match subdiv(a, b, n, t, avail) {
Step::Size(k) => n = k,
found => return found,
}
}
}
Step::Size(n)
}
}
pub(super) fn gcdext_dc_itch(n: usize) -> usize {
let p = n / 3;
let mn = (n - p).div_ceil(2) + 1;
let hg = matrix_itch(n - p)
+ hgcd_itch(n - p)
.max(adjust_itch(p, mn))
.max(row_itch(n + 2, mn));
hg.max(lehmer_ext_itch(n))
.max(tail_itch(GCDEXT_DC_THRESHOLD.min(n), n + 4))
}
fn row_itch(un: usize, mn: usize) -> usize {
2 * (un + mn + 1) + mul_any_itch(un, mn)
}
unsafe fn row_times(row: &mut Row, m: &Matrix, t: *mut u64) {
let (un, mn) = (row.un, m.n);
let len = un + mn;
unsafe {
let (x, y) = (t, t.add(len + 1));
let w = y.add(len + 1);
mul_any(x, row.u0, un, m.p[0][0], mn, w);
mul_any(y, row.u1, un, m.p[1][0], mn, w);
*x.add(len) = u64::from(add_n(x, x, y, len));
mul_any(y, row.u0, un, m.p[0][1], mn, w);
mul_any(row.u0, row.u1, un, m.p[1][1], mn, w);
*row.u1.add(len) = u64::from(add_n(row.u1, row.u0, y, len));
ptr::copy_nonoverlapping(x, row.u0, len + 1);
let mut n = len + 1;
while n > 1 && *row.u0.add(n - 1) | *row.u1.add(n - 1) == 0 {
n -= 1;
}
row.un = n;
}
}
pub(super) unsafe fn gcdext_dc(
a: *mut u64,
b: *mut u64,
mut n: usize,
row: &mut Row,
t: *mut u64,
avail: usize,
) -> StepExt {
let mut first = true;
unsafe {
while n >= GCDEXT_DC_THRESHOLD {
let p = if first { n / 2 } else { n / 3 };
first = false;
let mut m = Matrix::new(n - p, t);
let w = t.add(matrix_itch(n - p));
let nn = hgcd(a.add(p), b.add(p), n - p, &mut m, w);
if nn > 0 {
n = m.adjust(p + nn, a, b, p, w);
row_times(row, &m, w);
} else {
match subdiv_ext(a, b, n, row, t, avail) {
StepExt::Size(k) => n = k,
found => return found,
}
}
}
StepExt::Size(n)
}
}
fn tail_itch(n: usize, un: usize) -> usize {
let lehmer = 2 * (n + 1) + 2 * (n + 4) + (n + 4) + lehmer_ext_itch(n);
let v = (2 * n + 4) + (n + 3) + mul_any_itch(n, n + 3).max(divexact_itch(2 * n + 4, n));
let combine = 2 * (un + n + 5) + mul_any_itch(un, n + 3);
lehmer + v + combine
}
#[allow(clippy::too_many_arguments)]
unsafe fn tail(
g: *mut u64,
c: *mut u64,
a: *mut u64,
b: *mut u64,
n: usize,
row: &mut Row,
t: *mut u64,
avail: usize,
) -> (usize, usize, bool) {
unsafe {
if cmp_n(a, b, n) == Ordering::Equal {
let f = row.smaller();
ptr::copy_nonoverlapping(a, g, n);
ptr::copy(f.p, c, f.n);
return (normalized(g, n), f.n, f.negative);
}
let (ac, bc) = (t, t.add(n + 1));
let (r0, r1) = (bc.add(n + 1), bc.add(n + 1 + n + 4));
let cq = r1.add(n + 4);
let prod = cq.add(n + 4);
let v = prod.add(2 * n + 4);
let p1 = v.add(n + 3);
let p2 = p1.add(row.un + n + 5);
let w = p2.add(row.un + n + 5);
let room = avail - (w.offset_from(t) as usize);
ptr::copy_nonoverlapping(a, ac, n);
ptr::copy_nonoverlapping(b, bc, n);
ptr::write_bytes(r0, 0, 2 * (n + 4));
*r1 = 1;
let mut fresh = Row {
u0: r0,
u1: r1,
un: 1,
};
let (gn, un_, negative) = gcdext_lehmer(g, cq, ac, bc, n, &mut fresh, w, room);
let (u0, u1) = (row.u0, row.u1);
let (u0n, u1n) = (normalized(u0, row.un), normalized(u1, row.un));
if un_ == 0 {
ptr::copy(u0, c, u0n);
return (gn, u0n, u0n != 0);
}
let an = normalized(a, n);
let bn = normalized(b, n);
let mut pn = an + un_;
mul_any(prod, a, an, cq, un_, w);
if negative {
*prod.add(pn) = add_to(prod, prod, pn, g, gn);
pn += 1;
} else {
let borrow = sub_from(prod, prod, pn, g, gn);
debug_assert!(borrow == 0);
}
let pn = normalized(prod, pn);
let vn = if pn == 0 {
0
} else {
divexact(v, prod, pn, b, bn, w, room);
normalized(v, pn - bn + 1)
};
let n1 = un_ + u1n;
mul_any(p1, cq, un_, u1, u1n, w);
let len = if vn == 0 || u0n == 0 {
n1
} else {
let n2 = vn + u0n;
mul_any(p2, v, vn, u0, u0n, w);
let (long, ln, short, sn) = if n1 >= n2 {
(p1, n1, p2, n2)
} else {
(p2, n2, p1, n1)
};
*long.add(ln) = add_to(long, long, ln, short, sn);
if long != p1 {
ptr::copy_nonoverlapping(long, p1, ln + 1);
}
ln + 1
};
let len = normalized(p1, len);
ptr::copy_nonoverlapping(p1, c, len);
(gn, len, negative && len != 0)
}
}
#[allow(clippy::too_many_arguments)]
pub(super) unsafe fn gcdext(
g: *mut u64,
c: *mut u64,
a: *mut u64,
b: *mut u64,
n: usize,
row: &mut Row,
t: *mut u64,
avail: usize,
) -> (usize, usize, bool) {
unsafe {
if n < GCDEXT_DC_THRESHOLD {
return gcdext_lehmer(g, c, a, b, n, row, t, avail);
}
let identity = row.un == 1 && *row.u0 == 0 && *row.u1 == 1;
let n = match gcdext_dc(a, b, n, row, t, avail) {
StepExt::Size(k) => k,
StepExt::Gcd(found) => {
ptr::copy(found.g, g, found.gn);
let f = found.cofactor;
ptr::copy(f.p, c, f.n);
return (found.gn, f.n, f.negative);
}
};
if identity && row.un == 1 && *row.u0 == 0 && *row.u1 == 1 {
return gcdext_lehmer(g, c, a, b, n, row, t, avail);
}
tail(g, c, a, b, n, row, t, avail)
}
}