const M62: u64 = u64::MAX >> 2;
const P: [i64; 5] = [-19, 0, 0, 0, 128];
const P_INV62: u64 = {
let p0: u64 = 0u64.wrapping_sub(19); let mut inv = p0;
let mut i = 0;
while i < 5 {
inv = inv.wrapping_mul(2u64.wrapping_sub(p0.wrapping_mul(inv)));
i += 1;
}
inv & M62
};
#[inline(always)]
fn opaque(mut x: u64) -> u64 {
unsafe {
core::arch::asm!("/* {x} */", x = inout(reg) x, options(pure, nomem, nostack, preserves_flags));
}
x
}
struct Trans {
u: i64,
v: i64,
q: i64,
r: i64,
}
#[inline(always)]
fn divsteps_59(zeta: i64, f0: u64, g0: u64) -> (i64, Trans) {
let (mut u, mut v, mut q, mut r) = (8u64, 0u64, 0u64, 8u64);
let (mut f, mut g, mut z) = (f0, g0, zeta as u64);
unsafe {
core::arch::asm!(
"xor {zero:e}, {zero:e}",
".rept 59",
"mov {c1}, {z}",
"sar {c1}, 63",
"mov {x}, {f}",
"xor {x}, {c1}",
"sub {x}, {c1}",
"mov {y}, {u}",
"xor {y}, {c1}",
"sub {y}, {c1}",
"mov {w}, {v}",
"xor {w}, {c1}",
"sub {w}, {c1}",
"test {g}, 1",
"cmovz {x}, {zero}",
"cmovz {y}, {zero}",
"cmovz {w}, {zero}",
"cmovz {c1}, {zero}",
"add {g}, {x}",
"add {q}, {y}",
"add {r}, {w}",
"xor {z}, {c1}",
"sub {z}, 1",
"mov {x}, {g}",
"and {x}, {c1}",
"add {f}, {x}",
"mov {y}, {q}",
"and {y}, {c1}",
"add {u}, {y}",
"mov {w}, {r}",
"and {w}, {c1}",
"add {v}, {w}",
"shr {g}, 1",
"add {u}, {u}",
"add {v}, {v}",
".endr",
z = inout(reg) z,
f = inout(reg) f,
g = inout(reg) g,
u = inout(reg) u,
v = inout(reg) v,
q = inout(reg) q,
r = inout(reg) r,
c1 = out(reg) _,
zero = out(reg) _,
x = out(reg) _,
y = out(reg) _,
w = out(reg) _,
options(pure, nomem, nostack),
);
}
let _ = f;
let _ = g;
(
z as i64,
Trans {
u: u as i64,
v: v as i64,
q: q as i64,
r: r as i64,
},
)
}
#[inline(always)]
fn m(a: i64, b: i64) -> i128 {
(a as i128).wrapping_mul(b as i128)
}
#[inline(always)]
fn update_de(d: &mut [i64; 5], e: &mut [i64; 5], t: &Trans) {
let (u, v, q, r) = (t.u, t.v, t.q, t.r);
let sd = d[4] >> 63;
let se = e[4] >> 63;
let mut md = (u & sd).wrapping_add(v & se);
let mut me = (q & sd).wrapping_add(r & se);
let mut cd = m(u, d[0]).wrapping_add(m(v, e[0]));
let mut ce = m(q, d[0]).wrapping_add(m(r, e[0]));
md = md.wrapping_sub((P_INV62.wrapping_mul(cd as u64).wrapping_add(md as u64) & M62) as i64);
me = me.wrapping_sub((P_INV62.wrapping_mul(ce as u64).wrapping_add(me as u64) & M62) as i64);
cd = cd.wrapping_add(m(P[0], md));
ce = ce.wrapping_add(m(P[0], me));
debug_assert_eq!(cd as u64 & M62, 0);
debug_assert_eq!(ce as u64 & M62, 0);
cd >>= 62;
ce >>= 62;
for i in 1..5 {
cd = cd.wrapping_add(m(u, d[i])).wrapping_add(m(v, e[i]));
ce = ce.wrapping_add(m(q, d[i])).wrapping_add(m(r, e[i]));
if P[i] != 0 {
cd = cd.wrapping_add(m(P[i], md));
ce = ce.wrapping_add(m(P[i], me));
}
d[i - 1] = (cd as u64 & M62) as i64;
e[i - 1] = (ce as u64 & M62) as i64;
cd >>= 62;
ce >>= 62;
}
d[4] = cd as i64;
e[4] = ce as i64;
}
#[inline(always)]
fn update_fg(f: &mut [i64; 5], g: &mut [i64; 5], t: &Trans) {
let (u, v, q, r) = (t.u, t.v, t.q, t.r);
let mut cf = m(u, f[0]).wrapping_add(m(v, g[0]));
let mut cg = m(q, f[0]).wrapping_add(m(r, g[0]));
debug_assert_eq!(cf as u64 & M62, 0);
debug_assert_eq!(cg as u64 & M62, 0);
cf >>= 62;
cg >>= 62;
for i in 1..5 {
cf = cf.wrapping_add(m(u, f[i])).wrapping_add(m(v, g[i]));
cg = cg.wrapping_add(m(q, f[i])).wrapping_add(m(r, g[i]));
f[i - 1] = (cf as u64 & M62) as i64;
g[i - 1] = (cg as u64 & M62) as i64;
cf >>= 62;
cg >>= 62;
}
f[4] = cf as i64;
g[4] = cg as i64;
}
#[inline(always)]
fn normalize(r: &mut [i64; 5], sign: i64) {
let add = opaque((r[4] >> 63) as u64) as i64;
for i in 0..5 {
r[i] = r[i].wrapping_add(P[i] & add);
}
let neg = opaque((sign >> 63) as u64) as i64;
for limb in r.iter_mut() {
*limb = (*limb ^ neg).wrapping_sub(neg);
}
carry(r);
let add = opaque((r[4] >> 63) as u64) as i64;
for i in 0..5 {
r[i] = r[i].wrapping_add(P[i] & add);
}
carry(r);
}
#[inline(always)]
fn carry(r: &mut [i64; 5]) {
for i in 0..4 {
r[i + 1] = r[i + 1].wrapping_add(r[i] >> 62);
r[i] &= M62 as i64;
}
}
struct State {
d: [i64; 5],
e: [i64; 5],
f: [i64; 5],
g: [i64; 5],
zeta: i64,
}
#[inline(never)]
fn round(st: &mut State) {
let (z, t) = divsteps_59(st.zeta, st.f[0] as u64, st.g[0] as u64);
st.zeta = z;
update_de(&mut st.d, &mut st.e, &t);
update_fg(&mut st.f, &mut st.g, &t);
}
pub(super) fn invert(x: [u64; 4]) -> [u64; 4] {
let g0 = [
(x[0] & M62) as i64,
(((x[0] >> 62) | (x[1] << 2)) & M62) as i64,
(((x[1] >> 60) | (x[2] << 4)) & M62) as i64,
(((x[2] >> 58) | (x[3] << 6)) & M62) as i64,
(x[3] >> 56) as i64,
];
let mut st = State {
d: [0; 5],
e: [1, 0, 0, 0, 0],
f: P,
g: g0,
zeta: -1,
};
round(&mut st);
round(&mut st);
round(&mut st);
round(&mut st);
round(&mut st);
round(&mut st);
round(&mut st);
round(&mut st);
round(&mut st);
round(&mut st);
let State { mut d, f, .. } = st;
normalize(&mut d, f[4]);
let w = d.map(|l| l as u64);
[
w[0] | (w[1] << 62),
(w[1] >> 2) | (w[2] << 60),
(w[2] >> 4) | (w[3] << 58),
(w[3] >> 6) | (w[4] << 56),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_inverse_constant_is_an_inverse() {
let p_lo = (1u64 << 62) - 19;
assert_eq!(p_lo.wrapping_mul(P_INV62) & M62, 1);
}
}