#![allow(unsafe_code)]
#[allow(clippy::inline_always)]
#[inline(always)]
#[must_use]
pub fn apply(lhs: u64, rhs: u64) -> (u64, u64) {
const MIX: u64 = 0xbf58_476d_1ce4_e5b9;
const SHIFT2: u64 = 0x94d0_49bb_1331_11eb;
#[cfg(target_arch = "x86_64")]
{
if std::is_x86_feature_detected!("aes") {
const RK_LO: u64 = 0xcbf2_9ce4_8422_2325;
const RK_HI: u64 = 0x1000_0000_01b3_0000;
let lo: u64;
let hi: u64;
unsafe {
use core::arch::asm;
asm!(
"movq xmm0, {lhs}",
"movq xmm1, {rhs}",
"punpcklqdq xmm0, xmm1",
"movq xmm1, {rk_lo}",
"movq xmm2, {rk_hi}",
"punpcklqdq xmm1, xmm2",
"aesenc xmm0, xmm1",
"movq {lo}, xmm0",
"pextrq {hi}, xmm0, 1",
lhs = in(reg) lhs,
rhs = in(reg) rhs,
rk_lo = in(reg) RK_LO,
rk_hi = in(reg) RK_HI,
lo = out(reg) lo,
hi = out(reg) hi,
out("xmm0") _,
out("xmm1") _,
out("xmm2") _,
options(nostack, pure, nomem),
);
}
return (lo, hi);
}
}
#[cfg(target_arch = "aarch64")]
{
#[cfg(target_vendor = "apple")]
let has_aes = true;
#[cfg(not(target_vendor = "apple"))]
let has_aes = std::arch::is_aarch64_feature_detected!("aes");
const RK_LO: u64 = 0xcbf2_9ce4_8422_2325;
const RK_HI: u64 = 0x1000_0000_01b3_0000;
unsafe {
use core::arch::aarch64::{uint64x2_t, vcombine_u64, vcreate_u64};
use core::arch::asm;
let state: uint64x2_t = vcombine_u64(vcreate_u64(lhs), vcreate_u64(rhs));
let rkey: uint64x2_t = vcombine_u64(vcreate_u64(RK_LO), vcreate_u64(RK_HI));
let out: uint64x2_t;
if has_aes {
asm!(
".arch_extension aes",
"aese {v_state:v}.16b, {v_rkey:v}.16b",
"aesmc {v_state:v}.16b, {v_state:v}.16b",
v_state = inout(vreg) state => out,
v_rkey = in(vreg) rkey,
options(nostack, pure, nomem, preserves_flags),
);
} else {
asm!(
"eor {v_state:v}.16b, {v_state:v}.16b, {v_rkey:v}.16b",
"rev64 {v_tmp:v}.16b, {v_state:v}.16b",
"ext {v_state:v}.16b, {v_state:v}.16b, {v_state:v}.16b, #8",
"eor {v_state:v}.16b, {v_state:v}.16b, {v_tmp:v}.16b",
v_state = inout(vreg) state => out,
v_rkey = in(vreg) rkey,
v_tmp = out(vreg) _,
options(nostack, pure, nomem, preserves_flags),
);
}
let res = core::mem::transmute::<uint64x2_t, [u64; 2]>(out);
return (res[0], res[1]);
}
}
#[cfg(all(target_arch = "riscv64", target_feature = "zkn"))]
{
const RK_LO: u64 = 0xcbf2_9ce4_8422_2325; const RK_HI: u64 = 0x1000_0000_01b3_0000;
let lo: u64;
let hi: u64;
unsafe {
use core::arch::asm;
asm!(
"aes64esm {lo}, {lhs}, {rhs}",
"aes64esm {hi}, {rhs}, {lhs}",
"xor {lo}, {lo}, {rk_lo}",
"xor {hi}, {hi}, {rk_hi}",
lhs = in(reg) lhs,
rhs = in(reg) rhs,
rk_lo = in(reg) RK_LO,
rk_hi = in(reg) RK_HI,
lo = out(reg) lo,
hi = out(reg) hi,
options(nostack, pure, nomem),
);
}
return (lo, hi);
}
let mut x = lhs.wrapping_mul(MIX) ^ rhs;
let mut y = rhs.wrapping_mul(MIX) ^ lhs;
x ^= x >> 27;
y ^= y >> 27;
x = x.wrapping_mul(SHIFT2);
y = y.wrapping_mul(SHIFT2);
(x ^ (x >> 31), y ^ (y >> 31))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distinct_inputs_produce_distinct_outputs() {
let h1 = apply(0xdead_beef, 0xcafe_f00d);
let h2 = apply(0xdead_beef, 0xcafe_f00e);
let h3 = apply(0xdead_bef0, 0xcafe_f00d);
assert_ne!(h1, h2);
assert_ne!(h1, h3);
assert_ne!(h2, h3);
}
#[test]
fn function_is_pure() {
let h1 = apply(42, 99);
let h2 = apply(42, 99);
assert_eq!(h1, h2);
}
#[test]
fn zeros_do_not_collapse_to_zero() {
let h = apply(0, 0);
assert_ne!(h, (0, 0));
}
}