use crate::backend::Arith;
pub(crate) const P: u64 = 0xFFFF_FFFF_0000_0001;
pub(crate) const E: u64 = P.wrapping_neg();
#[inline(always)]
pub(crate) fn canon(x: u64) -> u64 {
if x >= P { x - P } else { x }
}
#[inline(always)]
pub(crate) fn add(a: u64, b: u64) -> u64 {
let (sum, over) = a.overflowing_add(b);
let (mut sum, over) = sum.overflowing_add(u64::from(over) * E);
if over {
sum += E;
}
sum
}
#[inline(always)]
pub(crate) fn add_c(a: u64, c: u64) -> u64 {
let (sum, over) = a.overflowing_add(c);
sum.wrapping_add(E & u64::from(over).wrapping_neg())
}
#[inline(always)]
pub(crate) fn reduce_small(v: u128) -> u64 {
let lo = v as u64;
let hi = (v >> 64) as u64;
debug_assert!(hi <= E);
add_c(lo, (hi << 32) - hi)
}
#[cfg_attr(all(target_arch = "aarch64", not(hemera_portable)), allow(dead_code))]
#[inline(always)]
pub(crate) fn reduce_wide(x: u128) -> u64 {
let x_lo = x as u64;
let x_hi = (x >> 64) as u64;
let x_hi_hi = x_hi >> 32;
let x_hi_lo = x_hi & E;
let (mut t0, borrow) = x_lo.overflowing_sub(x_hi_hi);
if borrow {
t0 = t0.wrapping_sub(E);
}
let t1 = x_hi_lo * E;
let (res, carry) = t0.overflowing_add(t1);
res.wrapping_add(E * u64::from(carry))
}
#[inline(always)]
pub(crate) fn mul(a: u64, b: u64) -> u64 {
#[cfg(all(target_arch = "aarch64", not(hemera_portable)))]
{
let r: u64;
unsafe {
core::arch::asm!(
"mul {lo}, {a}, {b}",
"umulh {hi}, {a}, {b}",
"lsr {t}, {hi}, #32",
"subs {lo}, {lo}, {t}",
"b.cs 2f",
"sub {lo}, {lo}, {e}",
"2:",
"lsl {u}, {hi}, #32",
"and {t}, {hi}, {e}",
"bic {hi}, {e}, {hi}",
"sub {t}, {u}, {t}",
"add {u}, {u}, {hi}",
"add {u}, {lo}, {u}",
"adds {r}, {lo}, {t}",
"csel {r}, {u}, {r}, cs",
a = in(reg) a,
b = in(reg) b,
e = in(reg) E,
lo = out(reg) _,
hi = out(reg) _,
t = out(reg) _,
u = out(reg) _,
r = out(reg) r,
options(pure, nomem, nostack),
);
}
r
}
#[cfg(not(all(target_arch = "aarch64", not(hemera_portable))))]
{
reduce_wide(u128::from(a) * u128::from(b))
}
}
#[inline(always)]
pub(crate) fn mul_add(a: u64, b: u64, c: u64) -> u64 {
#[cfg(all(target_arch = "aarch64", not(hemera_portable)))]
{
let r: u64;
unsafe {
core::arch::asm!(
"mul {lo}, {a}, {b}",
"umulh {hi}, {a}, {b}",
"adds {lo}, {lo}, {c}",
"adc {hi}, {hi}, xzr",
"lsr {t}, {hi}, #32",
"subs {lo}, {lo}, {t}",
"b.cs 2f",
"sub {lo}, {lo}, {e}",
"2:",
"lsl {u}, {hi}, #32",
"and {t}, {hi}, {e}",
"bic {hi}, {e}, {hi}",
"sub {t}, {u}, {t}",
"add {u}, {u}, {hi}",
"add {u}, {lo}, {u}",
"adds {r}, {lo}, {t}",
"csel {r}, {u}, {r}, cs",
a = in(reg) a,
b = in(reg) b,
c = in(reg) c,
e = in(reg) E,
lo = out(reg) _,
hi = out(reg) _,
t = out(reg) _,
u = out(reg) _,
r = out(reg) r,
options(pure, nomem, nostack),
);
}
r
}
#[cfg(not(all(target_arch = "aarch64", not(hemera_portable))))]
{
reduce_wide(u128::from(a) * u128::from(b) + u128::from(c))
}
}
#[inline(always)]
pub(crate) fn mul_t(a: u64, b: u64) -> u64 {
#[cfg(all(target_arch = "aarch64", not(hemera_portable)))]
{
let r: u64;
unsafe {
core::arch::asm!(
"mul {lo}, {a}, {b}",
"umulh {hi}, {a}, {b}",
"subs {lo}, {lo}, {hi}, lsr #32",
"b.cs 2f",
"sub {lo}, {lo}, {e}",
"2:",
"lsl {u}, {hi}, #32",
"sub {u}, {u}, {hi:w}, uxtw",
"adds {r}, {lo}, {u}",
"csel {u}, {e}, xzr, cs",
"add {r}, {r}, {u}",
a = in(reg) a,
b = in(reg) b,
e = in(reg) E,
lo = out(reg) _,
hi = out(reg) _,
u = out(reg) _,
r = out(reg) r,
options(pure, nomem, nostack),
);
}
r
}
#[cfg(not(all(target_arch = "aarch64", not(hemera_portable))))]
{
reduce_wide(u128::from(a) * u128::from(b))
}
}
#[inline(always)]
pub(crate) fn mul_add_t(a: u64, b: u64, c: u64) -> u64 {
#[cfg(all(target_arch = "aarch64", not(hemera_portable)))]
{
let r: u64;
unsafe {
core::arch::asm!(
"mul {lo}, {a}, {b}",
"umulh {hi}, {a}, {b}",
"adds {lo}, {lo}, {c}",
"adc {hi}, {hi}, xzr",
"subs {lo}, {lo}, {hi}, lsr #32",
"b.cs 2f",
"sub {lo}, {lo}, {e}",
"2:",
"lsl {u}, {hi}, #32",
"sub {u}, {u}, {hi:w}, uxtw",
"adds {r}, {lo}, {u}",
"csel {u}, {e}, xzr, cs",
"add {r}, {r}, {u}",
a = in(reg) a,
b = in(reg) b,
c = in(reg) c,
e = in(reg) E,
lo = out(reg) _,
hi = out(reg) _,
u = out(reg) _,
r = out(reg) r,
options(pure, nomem, nostack),
);
}
r
}
#[cfg(not(all(target_arch = "aarch64", not(hemera_portable))))]
{
reduce_wide(u128::from(a) * u128::from(b) + u128::from(c))
}
}
#[inline(always)]
pub(crate) fn pow7_t<A: Arith>(x: u64) -> u64 {
let x2 = A::mul_t(x, x);
let x3 = A::mul_t(x2, x);
let x4 = A::mul_t(x2, x2);
A::mul_t(x3, x4)
}
#[inline(always)]
pub(crate) fn pow7<A: Arith>(x: u64) -> u64 {
let x2 = A::mul(x, x);
let x3 = A::mul(x2, x);
let x4 = A::mul(x2, x2);
A::mul(x3, x4)
}
#[inline(always)]
fn sqn<A: Arith>(mut x: u64, n: usize) -> u64 {
for _ in 0..n {
x = A::mul(x, x);
}
x
}
#[inline(always)]
fn sqn_fill<A: Arith, F: FnMut(usize)>(
mut x: u64,
n: usize,
every: usize,
fill: &mut F,
next: &mut usize,
tasks: usize,
) -> u64 {
for k in 1..=n {
x = A::mul(x, x);
if k % every == 0 && *next < tasks {
fill(*next);
*next += 1;
}
}
x
}
#[inline(always)]
pub(crate) fn inv_nonzero_fill<A: Arith, F: FnMut(usize)>(
x: u64,
fill: &mut F,
tasks: usize,
) -> u64 {
let mut next = 0;
let x2 = A::mul(x, x);
let a2 = A::mul(x2, x); let x4 = A::mul(x2, x2);
let a3 = A::mul(x4, a2); let a4 = A::mul(sqn::<A>(a2, 2), a2); let a7 = A::mul(sqn::<A>(a4, 3), a3);
let a8 = A::mul(sqn::<A>(a4, 4), a4);
let a15 = A::mul(sqn::<A>(a8, 7), a7);
let a16 = A::mul(sqn::<A>(a8, 8), a8);
let a31 = A::mul(sqn_fill::<A, F>(a16, 15, 3, fill, &mut next, tasks), a15);
let a32 = A::mul(A::mul(a31, a31), x);
let r = A::mul(sqn_fill::<A, F>(a31, 33, 3, fill, &mut next, tasks), a32);
while next < tasks {
fill(next);
next += 1;
}
r
}