use core::hint::unreachable_unchecked;
#[inline(always)]
pub fn branch_hint() {
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::asm!("", options(nomem, nostack, preserves_flags));
}
}
#[inline(always)]
pub unsafe fn assume(p: bool) {
debug_assert!(p);
if !p {
unsafe {
unreachable_unchecked();
}
}
}
#[inline]
pub(crate) fn reduce128(x: u128) -> u64 {
let (x_lo, x_hi) = split(x); let x_hi_hi = x_hi >> 32;
let x_hi_lo = x_hi & 0xFFFF_FFFF;
let (mut t0, borrow) = x_lo.overflowing_sub(x_hi_hi);
if borrow {
branch_hint(); t0 -= 0xFFFF_FFFF; }
let t1 = x_hi_lo * 0xFFFF_FFFF;
unsafe { add_no_canonicalize_trashing_input(t0, t1) }
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
const fn split(x: u128) -> (u64, u64) {
(x as u64, (x >> 64) as u64)
}
#[inline(always)]
#[cfg(target_arch = "x86_64")]
unsafe fn add_no_canonicalize_trashing_input(x: u64, y: u64) -> u64 {
unsafe {
let res_wrapped: u64;
let adjustment: u64;
core::arch::asm!(
"add {0}, {1}",
"sbb {1:e}, {1:e}",
inlateout(reg) x => res_wrapped,
inlateout(reg) y => adjustment,
options(pure, nomem, nostack),
);
assume(x != 0 || (res_wrapped == y && adjustment == 0));
assume(y != 0 || (res_wrapped == x && adjustment == 0));
res_wrapped + adjustment
}
}
#[inline(always)]
#[cfg(not(target_arch = "x86_64"))]
unsafe fn add_no_canonicalize_trashing_input(x: u64, y: u64) -> u64 {
let (res_wrapped, carry) = x.overflowing_add(y);
res_wrapped + 0xFFFF_FFFF * u64::from(carry)
}