#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
use super::src_tail_avx2;
const fn neg_inv_mod2_64(p: u64) -> u64 {
let mut inv = 1u64;
let mut i = 0;
while i < 6 {
inv = inv.wrapping_mul(2u64.wrapping_sub(p.wrapping_mul(inv)));
i += 1;
}
inv.wrapping_neg()
}
#[allow(clippy::cast_possible_truncation)]
fn redc64_scalar(t: u128, p: u64, ninv: u64) -> u64 {
let m = (t as u64).wrapping_mul(ninv);
let (num, carry) = t.overflowing_add(u128::from(m) * u128::from(p));
let u = (num >> 64) as u64;
if carry || u >= p {
u.wrapping_sub(p)
} else {
u
}
}
#[archmage::rite(v3)]
fn gt_epu64(a: __m256i, b: __m256i) -> __m256i {
let bias = _mm256_set1_epi64x(i64::MIN);
_mm256_cmpgt_epi64(_mm256_xor_si256(a, bias), _mm256_xor_si256(b, bias))
}
#[archmage::rite(v3)]
fn mul_wide(x: __m256i, y: __m256i) -> (__m256i, __m256i) {
let x_hi = _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(x)));
let y_hi = _mm256_castps_si256(_mm256_movehdup_ps(_mm256_castsi256_ps(y)));
let ll = _mm256_mul_epu32(x, y);
let lh = _mm256_mul_epu32(x, y_hi);
let hl = _mm256_mul_epu32(x_hi, y);
let hh = _mm256_mul_epu32(x_hi, y_hi);
let ll_hi = _mm256_srli_epi64::<32>(ll);
let t0 = _mm256_add_epi64(hl, ll_hi);
let t0_lo = _mm256_and_si256(t0, _mm256_set1_epi64x(0xFFFF_FFFF));
let t0_hi = _mm256_srli_epi64::<32>(t0);
let t1 = _mm256_add_epi64(lh, t0_lo);
let t2 = _mm256_add_epi64(hh, t0_hi);
let hi = _mm256_add_epi64(t2, _mm256_srli_epi64::<32>(t1));
let t1_lo = _mm256_castps_si256(_mm256_moveldup_ps(_mm256_castsi256_ps(t1)));
let lo = _mm256_blend_epi32::<0b1010_1010>(ll, t1_lo);
(hi, lo)
}
#[archmage::rite(v3)]
fn redc64_lanes(t_hi: __m256i, t_lo: __m256i, ninv: __m256i, p: __m256i, pm1: __m256i) -> __m256i {
let m = mul_wide(t_lo, ninv).1;
let (mp_hi, mp_lo) = mul_wide(m, p);
let n_lo = _mm256_add_epi64(t_lo, mp_lo);
let c_lo = gt_epu64(t_lo, n_lo);
let hi_sum = _mm256_add_epi64(t_hi, mp_hi);
let c_a = gt_epu64(t_hi, hi_sum);
let n_hi = _mm256_sub_epi64(hi_sum, c_lo);
let c_b = gt_epu64(hi_sum, n_hi);
let top = _mm256_or_si256(c_a, c_b);
let mask = _mm256_or_si256(top, gt_epu64(n_hi, pm1));
_mm256_sub_epi64(n_hi, _mm256_and_si256(mask, p))
}
#[archmage::rite(v3)]
fn addmod64(a: __m256i, b: __m256i, p: __m256i, pm1: __m256i) -> __m256i {
let sum = _mm256_add_epi64(a, b);
let borrow = gt_epu64(sum, pm1);
let wrapped = gt_epu64(a, sum);
let mask = _mm256_or_si256(borrow, wrapped);
_mm256_sub_epi64(sum, _mm256_and_si256(mask, p))
}
#[archmage::rite(v3)]
fn normalize64(x: __m256i, mu: __m256i, p: __m256i, pm1: __m256i) -> __m256i {
let q = mul_wide(x, mu).0;
let (_, qp_lo) = mul_wide(q, p);
let r = _mm256_sub_epi64(x, qp_lo);
let under = gt_epu64(r, pm1);
_mm256_sub_epi64(r, _mm256_and_si256(under, p))
}
#[allow(clippy::cast_possible_wrap, clippy::used_underscore_binding)]
#[archmage::arcane(import_intrinsics)]
pub fn mul_into_u64_avx2(
_token: archmage::X64V3Token,
dst: &mut [u8],
src: &[u8],
c_mont: u64,
p: u64,
) {
assert_eq!(
dst.len(),
src.len(),
"residue::mul_into_u64_avx2: dst and src lengths differ"
);
assert_eq!(
dst.len() % 8,
0,
"residue::mul_into_u64_avx2: dst length {} is not whole 8-byte lanes",
dst.len()
);
assert!(p % 2 == 1, "residue doubleword: modulus {p} is even");
assert!(
c_mont < p,
"residue doubleword: prepared coefficient {c_mont} reaches modulus {p}"
);
let ninv = neg_inv_mod2_64(p);
let ninv_v = _mm256_set1_epi64x(ninv.cast_signed());
let p_v = _mm256_set1_epi64x(p.cast_signed());
let pm1_v = _mm256_set1_epi64x(p.wrapping_sub(1).cast_signed());
let cm_v = _mm256_set1_epi64x(c_mont.cast_signed());
let (dst_lanes, dst_tail) = dst.as_chunks_mut::<32>();
let (src_lanes, _) = src.as_chunks::<32>();
for (d, s_lane) in dst_lanes.iter_mut().zip(src_lanes) {
let (t_hi, t_lo) = mul_wide(_mm256_loadu_si256(s_lane), cm_v);
_mm256_storeu_si256(d, redc64_lanes(t_hi, t_lo, ninv_v, p_v, pm1_v));
}
let (dt, _) = dst_tail.as_chunks_mut::<8>();
let (st, _) = src_tail_avx2(src).as_chunks::<8>();
for (d, x) in dt.iter_mut().zip(st) {
let t = u128::from(u64::from_le_bytes(*x)) * u128::from(c_mont);
*d = redc64_scalar(t, p, ninv).to_le_bytes();
}
}
#[allow(clippy::cast_possible_wrap, clippy::used_underscore_binding)]
#[archmage::arcane(import_intrinsics)]
pub fn mul_add_u64_avx2(
_token: archmage::X64V3Token,
dst: &mut [u8],
src: &[u8],
c_mont: u64,
p: u64,
) {
assert_eq!(
dst.len(),
src.len(),
"residue::mul_add_u64_avx2: dst and src lengths differ"
);
assert_eq!(
dst.len() % 8,
0,
"residue::mul_add_u64_avx2: dst length {} is not whole 8-byte lanes",
dst.len()
);
assert!(p % 2 == 1, "residue doubleword: modulus {p} is even");
assert!(
c_mont < p,
"residue doubleword: prepared coefficient {c_mont} reaches modulus {p}"
);
let ninv = neg_inv_mod2_64(p);
let ninv_v = _mm256_set1_epi64x(ninv.cast_signed());
let p_v = _mm256_set1_epi64x(p.cast_signed());
let pm1_v = _mm256_set1_epi64x(p.wrapping_sub(1).cast_signed());
let cm_v = _mm256_set1_epi64x(c_mont.cast_signed());
#[allow(clippy::cast_possible_truncation)]
let mu = ((1u128 << 64) / u128::from(p)) as u64;
let mu_v = _mm256_set1_epi64x(mu.cast_signed());
let (dst_lanes, dst_tail) = dst.as_chunks_mut::<32>();
let (src_lanes, _) = src.as_chunks::<32>();
for (d, s_lane) in dst_lanes.iter_mut().zip(src_lanes) {
let normed = normalize64(_mm256_loadu_si256(&*d), mu_v, p_v, pm1_v);
let (t_hi, t_lo) = mul_wide(_mm256_loadu_si256(s_lane), cm_v);
let prod = redc64_lanes(t_hi, t_lo, ninv_v, p_v, pm1_v);
_mm256_storeu_si256(d, addmod64(normed, prod, p_v, pm1_v));
}
let (dt, _) = dst_tail.as_chunks_mut::<8>();
let (st, _) = src_tail_avx2(src).as_chunks::<8>();
for (d, x) in dt.iter_mut().zip(st) {
let acc = u64::from_le_bytes(*d) % p;
let t = u128::from(u64::from_le_bytes(*x)) * u128::from(c_mont);
let prod = redc64_scalar(t, p, ninv);
let sum = u128::from(acc) + u128::from(prod);
#[allow(clippy::cast_possible_truncation)]
let out = (if sum >= u128::from(p) {
sum - u128::from(p)
} else {
sum
}) as u64;
*d = out.to_le_bytes();
}
}
#[allow(clippy::cast_possible_wrap, clippy::used_underscore_binding)]
#[archmage::arcane(import_intrinsics)]
pub fn mul_assign_u64_avx2(_token: archmage::X64V3Token, dst: &mut [u8], c_mont: u64, p: u64) {
assert_eq!(
dst.len() % 8,
0,
"residue::mul_assign_u64_avx2: dst length {} is not whole 8-byte lanes",
dst.len()
);
assert!(p % 2 == 1, "residue doubleword: modulus {p} is even");
assert!(
c_mont < p,
"residue doubleword: prepared coefficient {c_mont} reaches modulus {p}"
);
let ninv = neg_inv_mod2_64(p);
let ninv_v = _mm256_set1_epi64x(ninv.cast_signed());
let p_v = _mm256_set1_epi64x(p.cast_signed());
let pm1_v = _mm256_set1_epi64x(p.wrapping_sub(1).cast_signed());
let cm_v = _mm256_set1_epi64x(c_mont.cast_signed());
let (dst_lanes, dst_tail) = dst.as_chunks_mut::<32>();
for d in dst_lanes.iter_mut() {
let (t_hi, t_lo) = mul_wide(_mm256_loadu_si256(&*d), cm_v);
_mm256_storeu_si256(d, redc64_lanes(t_hi, t_lo, ninv_v, p_v, pm1_v));
}
let (dt, _) = dst_tail.as_chunks_mut::<8>();
for d in dt.iter_mut() {
let t = u128::from(u64::from_le_bytes(*d)) * u128::from(c_mont);
*d = redc64_scalar(t, p, ninv).to_le_bytes();
}
}