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_20(zeta: i64, f0: u64, g0: u64) -> (i64, [i64; 4]) {
const LOW: u64 = (1 << 20) - 1;
let mut f = ((f0 & LOW) as i64).wrapping_sub(1 << 41);
let mut g = ((g0 & LOW) as i64).wrapping_sub(1 << 62);
let mut z = zeta;
unsafe {
core::arch::asm!(
".p2align 6",
".rept 10",
"lea {g1}, [{g} + {f}]",
"mov {g2}, {g}",
"sub {g2}, {f}",
"mov {zs}, -2",
"sub {zs}, {z}",
"lea {zm}, [{z} - 1]",
"mov {f2}, {f}",
"test {z}, {z}",
"cmovs {g1}, {g2}",
"cmovs {f2}, {g}",
"cmovns {zs}, {zm}",
"test {g}, 1",
"cmovz {g1}, {g}",
"cmovz {f2}, {f}",
"cmovz {zs}, {zm}",
"sar {g1}, 1",
"lea {g}, [{g1} + {f2}]",
"mov {g2}, {g1}",
"sub {g2}, {f2}",
"mov {z}, -2",
"sub {z}, {zs}",
"lea {zm}, [{zs} - 1]",
"mov {f}, {f2}",
"test {zs}, {zs}",
"cmovs {g}, {g2}",
"cmovs {f}, {g1}",
"cmovns {z}, {zm}",
"test {g1}, 1",
"cmovz {g}, {g1}",
"cmovz {f}, {f2}",
"cmovz {z}, {zm}",
"sar {g}, 1",
".endr",
f = inout(reg) f,
g = inout(reg) g,
z = inout(reg) z,
f2 = out(reg) _,
g1 = out(reg) _,
g2 = out(reg) _,
zs = out(reg) _,
zm = out(reg) _,
options(pure, nomem, nostack),
);
}
let (u, v) = unpack(f);
let (q, r) = unpack(g);
(
z,
[
u.wrapping_neg(),
v.wrapping_neg(),
q.wrapping_neg(),
r.wrapping_neg(),
],
)
}
#[inline(always)]
fn unpack(w: i64) -> (i64, i64) {
let t = w.wrapping_add(1 << 20) >> 21;
let hi = t.wrapping_add(1 << 20) >> 21;
(t.wrapping_sub(hi << 21), hi)
}
#[inline(always)]
fn next_low(a: i64, b: i64, x: u64, y: u64) -> u64 {
((a.wrapping_mul(x as i64)).wrapping_add(b.wrapping_mul(y as i64)) >> 20) as u64
}
#[inline(always)]
fn divsteps_60(zeta: i64, f0: u64, g0: u64) -> (i64, Trans) {
let (z, [u1, v1, q1, r1]) = divsteps_20(zeta, f0, g0);
let (f1, g1) = (next_low(u1, v1, f0, g0), next_low(q1, r1, f0, g0));
let (z, [u2, v2, q2, r2]) = divsteps_20(z, f1, g1);
let (f2, g2) = (next_low(u2, v2, f1, g1), next_low(q2, r2, f1, g1));
let (z, [u3, v3, q3, r3]) = divsteps_20(z, f2, g2);
let w = |a: i64, b: i64, c: i64, d: i64| a.wrapping_mul(b).wrapping_add(c.wrapping_mul(d));
let (u21, v21) = (w(u2, u1, v2, q1), w(u2, v1, v2, r1));
let (q21, r21) = (w(q2, u1, r2, q1), w(q2, v1, r2, r1));
let (u, v) = (w(u3, u21, v3, q21), w(u3, v21, v3, r21));
let (q, r) = (w(q3, u21, r3, q21), w(q3, v21, r3, r21));
(
z,
Trans {
u: u << 2,
v: v << 2,
q: q << 2,
r: r << 2,
},
)
}
#[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_60(st.zeta, st.f[0] as u64, st.g[0] as u64);
st.zeta = z;
update_fg(&mut st.f, &mut st.g, &t);
update_de(&mut st.d, &mut st.e, &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::*;
fn reference_60(zeta: i64, f0: i128, g0: i128) -> (i64, [i128; 4]) {
let (mut z, mut f, mut g) = (zeta, f0, g0);
let (mut u, mut v, mut q, mut r) = (1i128 << 60, 0i128, 0i128, 1i128 << 60);
for _ in 0..60 {
assert!(f & 1 == 1, "f stays odd");
if z < 0 && g & 1 == 1 {
(z, f, g) = (-z - 2, g, (g - f) / 2);
(u, v, q, r) = (q, r, (q - u) / 2, (r - v) / 2);
} else if g & 1 == 1 {
(z, g) = (z - 1, (g + f) / 2);
(q, r) = ((q + u) / 2, (r + v) / 2);
} else {
(z, g) = (z - 1, g / 2);
(q, r) = (q / 2, r / 2);
}
}
assert_eq!(u * f0 + v * g0, f << 60);
assert_eq!(q * f0 + r * g0, g << 60);
(z, [u, v, q, r])
}
#[test]
fn packed_divsteps_agree_with_the_definition() {
let mut x = 0x9e37_79b9_7f4a_7c15u64;
let mut next = move || {
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
x.wrapping_mul(0x2545_f491_4f6c_dd1d)
};
let n = if cfg!(debug_assertions) {
20_000
} else {
400_000
};
let mut checked = 0;
for i in 0..n {
let shape = |w: u64, k: u64| match k % 4 {
0 => w,
1 => w & (w >> 7) & (w >> 13),
2 => w | (w << 9) | (w << 17),
_ => w >> (k % 61),
};
let f0 = shape(next(), i) | 1;
let g0 = shape(next(), i / 4);
let zeta = (next() % 1201) as i64 - 600;
let (z, t) = divsteps_60(zeta, f0, g0);
for (f, g) in [
(f0 as i128, g0 as i128),
(f0 as i64 as i128, g0 as i64 as i128),
] {
let (rz, m) = reference_60(zeta, f, g);
assert_eq!(z, rz, "zeta for {f0:x} {g0:x} {zeta}");
assert_eq!(
[t.u as i128, t.v as i128, t.q as i128, t.r as i128],
m.map(|e| e * 4),
"matrix for {f0:x} {g0:x} {zeta}"
);
}
checked += 1;
}
assert_eq!(checked, n);
}
#[test]
fn unpacking_holds_at_the_edges() {
let lows = [-(1i64 << 20) + 1, -1, 0, 1, (1 << 20) - 1];
let fields = [-(1i64 << 20), -(1 << 20) + 1, -1, 0, 1, (1 << 20) - 1];
let mut checked = 0;
for f in lows {
for u in fields {
for v in fields {
let w = f.wrapping_add(u << 21).wrapping_add(v << 42);
assert_eq!(unpack(w), (u, v), "f {f} u {u} v {v}");
checked += 1;
}
}
}
assert_eq!(checked, 5 * 6 * 6);
}
#[test]
fn the_inverse_constant_is_an_inverse() {
let p_lo = (1u64 << 62) - 19;
assert_eq!(p_lo.wrapping_mul(P_INV62) & M62, 1);
}
}