use crate::{
core::{big_sigma0, big_sigma1, compress, small_sigma0, small_sigma1, H0, K},
lanes::{Lanes, Scalar},
};
pub(crate) const PAD: [u32; 8] = [0x8000_0000, 0, 0, 0, 0, 0, 0, 256];
const MAX_WIDTH: usize = 32;
pub(crate) type StepsFn = unsafe fn(&mut [[u32; 8]], u64);
#[inline(always)]
fn seed_words(seed: &[u8; 32]) -> [u32; 8] {
let mut h = [0u32; 8];
for (hj, c) in h.iter_mut().zip(seed.chunks_exact(4)) {
*hj = u32::from_be_bytes(c.try_into().unwrap());
}
h
}
#[inline(always)]
fn word_bytes(h: &[u32; 8]) -> [u8; 32] {
let mut out = [0u8; 32];
for (c, hj) in out.chunks_exact_mut(4).zip(h) {
c.copy_from_slice(&hj.to_be_bytes());
}
out
}
pub(crate) fn portable(seed: &[u8; 32], n: u64) -> [u8; 32] {
let mut h = seed_words(seed);
let mut w = [0u32; 16];
w[8..].copy_from_slice(&PAD);
for _ in 0..n {
w[..8].copy_from_slice(&h);
let mut st = H0.map(Scalar::<1>::splat);
compress::<Scalar<1>>(&mut st, w.map(Scalar::<1>::splat));
for (hi, s) in h.iter_mut().zip(st) {
*hi = s.0[0];
}
}
word_bytes(&h)
}
const fn sig0(x: u32) -> u32 {
x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3)
}
const fn sig1(x: u32) -> u32 {
x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10)
}
const PADK: [u32; 8] = {
let mut a = [0u32; 8];
let mut i = 0;
while i < 8 {
a[i] = K[8 + i].wrapping_add(PAD[i]);
i += 1;
}
a
};
#[inline(always)]
pub(crate) fn compress_chain<L: Lanes>(state: &mut [L; 8], w8: &[L; 8]) {
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
let mut w = [L::splat(0); 16];
w[..8].copy_from_slice(w8);
macro_rules! round {
($a:ident, $b:ident, $c:ident, $d:ident,
$e:ident, $f:ident, $g:ident, $h:ident, $wk:expr) => {{
let t1 = $wk.add($h.add(big_sigma1($e)).add($e.ch($f, $g)));
let t2 = big_sigma0($a).add($a.maj($b, $c));
$d = $d.add(t1);
$h = t1.add(t2);
}};
}
macro_rules! rounds8 {
($sched:ident, $base:literal) => {
round!(a, b, c, d, e, f, g, h, $sched!($base, 0));
round!(h, a, b, c, d, e, f, g, $sched!($base, 1));
round!(g, h, a, b, c, d, e, f, $sched!($base, 2));
round!(f, g, h, a, b, c, d, e, $sched!($base, 3));
round!(e, f, g, h, a, b, c, d, $sched!($base, 4));
round!(d, e, f, g, h, a, b, c, $sched!($base, 5));
round!(c, d, e, f, g, h, a, b, $sched!($base, 6));
round!(b, c, d, e, f, g, h, a, $sched!($base, 7));
};
}
macro_rules! live {
($b:literal, $j:literal) => {
w[$b + $j].add(L::splat(K[$b + $j]))
};
}
macro_rules! padk {
($b:literal, $j:literal) => {
L::splat(PADK[$j])
};
}
macro_rules! band {
($b:literal, $j:literal) => {
w[$b - 16 + $j].add(L::splat(K[$b + $j]))
};
}
macro_rules! ext {
($b:literal, $j:literal) => {{
const I: usize = ($b + $j) & 15;
let s1 = small_sigma1(w[(I + 14) & 15]);
let s0 = small_sigma0(w[(I + 1) & 15]);
w[I] = s1.add(w[(I + 9) & 15]).add(s0).add(w[I]);
w[I].add(L::splat(K[$b + $j]))
}};
}
rounds8!(live, 0);
rounds8!(padk, 8);
w[0] = w[0].add(small_sigma0(w[1]));
w[1] = w[1].add(small_sigma0(w[2])).add(L::splat(sig1(PAD[7])));
w[2] = w[2].add(small_sigma0(w[3])).add(small_sigma1(w[0]));
w[3] = w[3].add(small_sigma0(w[4])).add(small_sigma1(w[1]));
w[4] = w[4].add(small_sigma0(w[5])).add(small_sigma1(w[2]));
w[5] = w[5].add(small_sigma0(w[6])).add(small_sigma1(w[3]));
w[6] = w[6]
.add(small_sigma0(w[7]))
.add(L::splat(PAD[7]))
.add(small_sigma1(w[4]));
w[7] = w[7]
.add(L::splat(sig0(PAD[0])))
.add(w[0])
.add(small_sigma1(w[5]));
w[8] = L::splat(PAD[0]).add(w[1]).add(small_sigma1(w[6]));
w[9] = w[2].add(small_sigma1(w[7]));
w[10] = w[3].add(small_sigma1(w[8]));
w[11] = w[4].add(small_sigma1(w[9]));
w[12] = w[5].add(small_sigma1(w[10]));
w[13] = w[6].add(small_sigma1(w[11]));
w[14] = L::splat(sig0(PAD[7])).add(w[7]).add(small_sigma1(w[12]));
w[15] = L::splat(PAD[7])
.add(small_sigma0(w[0]))
.add(w[8])
.add(small_sigma1(w[13]));
rounds8!(band, 16);
rounds8!(band, 24);
rounds8!(ext, 32);
rounds8!(ext, 40);
rounds8!(ext, 48);
rounds8!(ext, 56);
state[0] = state[0].add(a);
state[1] = state[1].add(b);
state[2] = state[2].add(c);
state[3] = state[3].add(d);
state[4] = state[4].add(e);
state[5] = state[5].add(f);
state[6] = state[6].add(g);
state[7] = state[7].add(h);
}
#[inline(always)]
pub(crate) fn steps_lanes<L: Lanes, const W: usize>(h: &mut [[u32; 8]], n: u64) {
const { assert!(L::N == W) };
debug_assert_eq!(h.len(), W);
let mut scratch = [0u32; W];
let mut hv = [L::splat(0); 8];
for (j, hj) in hv.iter_mut().enumerate() {
for (lane, s) in scratch.iter_mut().enumerate() {
*s = h[lane][j];
}
*hj = L::load(&scratch);
}
let init = H0.map(L::splat);
if L::FLAT_ROUNDS {
for _ in 0..n {
let mut st = init;
compress_chain::<L>(&mut st, &hv);
hv = st;
}
} else {
let pad = PAD.map(L::splat);
for _ in 0..n {
let mut w = [L::splat(0); 16];
w[..8].copy_from_slice(&hv);
w[8..].copy_from_slice(&pad);
let mut st = init;
compress::<L>(&mut st, w);
hv = st;
}
}
for (j, hj) in hv.iter().enumerate() {
hj.store(&mut scratch);
for (lane, s) in scratch.iter().enumerate() {
h[lane][j] = *s;
}
}
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
#[inline(always)]
pub(crate) fn steps_lanes2<L: Lanes, const W: usize>(h: &mut [[u32; 8]], n: u64) {
const { assert!(L::N == W) };
debug_assert_eq!(h.len(), 2 * W);
let mut scratch = [0u32; W];
let mut wa = [L::splat(0); 8];
let mut wb = [L::splat(0); 8];
for j in 0..8 {
for (lane, s) in scratch.iter_mut().enumerate() {
*s = h[lane][j];
}
wa[j] = L::load(&scratch);
for (lane, s) in scratch.iter_mut().enumerate() {
*s = h[W + lane][j];
}
wb[j] = L::load(&scratch);
}
let pad = PAD.map(L::splat);
let init = H0.map(L::splat);
for _ in 0..n {
let mut ba = [L::splat(0); 16];
ba[..8].copy_from_slice(&wa);
ba[8..].copy_from_slice(&pad);
let mut bb = [L::splat(0); 16];
bb[..8].copy_from_slice(&wb);
bb[8..].copy_from_slice(&pad);
let (mut sta, mut stb) = (init, init);
crate::avx512x2::compress2::<L>(&mut sta, &mut stb, ba, bb);
wa = sta;
wb = stb;
}
for j in 0..8 {
wa[j].store(&mut scratch);
for (lane, s) in scratch.iter().enumerate() {
h[lane][j] = *s;
}
wb[j].store(&mut scratch);
for (lane, s) in scratch.iter().enumerate() {
h[W + lane][j] = *s;
}
}
}
pub(crate) unsafe fn steps_scalar1(h: &mut [[u32; 8]], n: u64) {
steps_lanes::<Scalar<1>, 1>(h, n)
}
pub(crate) unsafe fn steps_scalar8(h: &mut [[u32; 8]], n: u64) {
steps_lanes::<Scalar<8>, 8>(h, n)
}
#[allow(clippy::too_many_arguments)]
fn feed(
lane: usize,
order: &[u32],
next: &mut usize,
seeds: &[[u8; 32]],
lens: &[u64],
out: &mut [[u8; 32]],
h: &mut [[u32; 8]],
rem: &mut [u64],
who: &mut [usize],
) -> bool {
while *next < order.len() {
let i = order[*next] as usize;
*next += 1;
if lens[i] == 0 {
out[i] = seeds[i];
continue;
}
h[lane] = seed_words(&seeds[i]);
rem[lane] = lens[i];
who[lane] = i;
return true;
}
false
}
pub(crate) unsafe fn run_scheduled(
width: usize,
steps: StepsFn,
seeds: &[[u8; 32]],
lens: &[u64],
out: &mut [[u8; 32]],
) {
let n = seeds.len();
debug_assert!((1..=MAX_WIDTH).contains(&width));
debug_assert!(n == lens.len() && n == out.len());
debug_assert!(u32::try_from(n).is_ok());
let mut small = [0u32; MAX_WIDTH];
let mut big: Vec<u32>;
let order: &[u32] = if n <= MAX_WIDTH {
for (s, i) in small.iter_mut().zip(0..n as u32) {
*s = i;
}
for i in 1..n {
let mut j = i;
while j > 0 && lens[small[j] as usize] > lens[small[j - 1] as usize] {
small.swap(j - 1, j);
j -= 1;
}
}
&small[..n]
} else {
big = (0..n as u32).collect();
big.sort_unstable_by(|&a, &b| lens[b as usize].cmp(&lens[a as usize]).then(a.cmp(&b)));
&big
};
let mut h = [[0u32; 8]; MAX_WIDTH];
let mut rem = [u64::MAX; MAX_WIDTH];
let mut who = [0usize; MAX_WIDTH];
let mut next = 0usize;
let mut live = 0usize;
for lane in 0..width {
if feed(
lane, order, &mut next, seeds, lens, out, &mut h, &mut rem, &mut who,
) {
live += 1;
}
}
while live > 0 {
let run = rem[..width].iter().copied().min().unwrap();
steps(&mut h[..width], run);
for lane in 0..width {
if rem[lane] == u64::MAX {
continue;
}
rem[lane] -= run;
if rem[lane] == 0 {
out[who[lane]] = word_bytes(&h[lane]);
rem[lane] = u64::MAX;
live -= 1;
if feed(
lane, order, &mut next, seeds, lens, out, &mut h, &mut rem, &mut who,
) {
live += 1;
}
}
}
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kernel {
Portable,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
ShaNi,
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
NeonSha2,
}
impl Kernel {
fn name(self) -> &'static str {
match self {
Kernel::Portable => "portable",
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNi => "shani",
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2 => "neon-sha2",
}
}
}
#[inline]
#[allow(clippy::needless_return)]
fn select() -> Kernel {
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
{
if crate::dispatch::have_shani() {
return Kernel::ShaNi;
}
return Kernel::Portable;
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
{
if std::arch::is_aarch64_feature_detected!("sha2") {
return Kernel::NeonSha2;
}
return Kernel::Portable;
}
#[cfg(not(all(
any(target_arch = "x86_64", target_arch = "aarch64"),
not(feature = "scalar")
)))]
return Kernel::Portable;
}
pub(crate) fn hash_chain(seed: &[u8; 32], n: u64) -> [u8; 32] {
match select() {
Kernel::Portable => portable(seed, n),
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNi => unsafe { crate::shani::chain(seed, n) },
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2 => unsafe { crate::neon_sha2::chain(seed, n) },
}
}
pub(crate) fn backend() -> &'static str {
select().name()
}
pub(crate) fn hash_chains(seeds: &[[u8; 32]], lens: &[u64], out: &mut [[u8; 32]]) {
match pick(crate::dispatch::select(), seeds.len()) {
Some(k) => {
let (width, steps) = steps_for(k, seeds.len());
unsafe { run_scheduled(width, steps, seeds, lens, out) }
}
None => {
for i in 0..seeds.len() {
out[i] = hash_chain(&seeds[i], lens[i]);
}
}
}
}
fn pick(wide: crate::dispatch::Kernel, remaining: usize) -> Option<crate::dispatch::Kernel> {
if remaining >= chain_min(wide) {
return Some(wide);
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
if remaining >= 3 && crate::dispatch::have_shani() {
return Some(crate::dispatch::Kernel::ShaNiX4);
}
None
}
fn chain_min(kernel: crate::dispatch::Kernel) -> usize {
use crate::dispatch::Kernel;
#[allow(unused_variables)]
let hw = !matches!(select(), self::Kernel::Portable);
match kernel {
Kernel::Portable1 | Kernel::Portable8 => usize::MAX,
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::Neon4 => {
if hw {
usize::MAX
} else {
2
}
}
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2x4 => 3,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNiX4 => 3,
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx2_8 => {
if hw {
usize::MAX
} else {
2
}
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx512_16 => {
if hw {
10
} else {
2
}
}
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx512_16x2 => {
if hw {
10
} else {
2
}
}
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
Kernel::Simd128_4 => 2,
}
}
#[allow(unused_variables)]
fn steps_for(kernel: crate::dispatch::Kernel, n: usize) -> (usize, StepsFn) {
use crate::dispatch::Kernel;
match kernel {
Kernel::Portable1 => (1, steps_scalar1 as StepsFn),
Kernel::Portable8 => (8, steps_scalar8 as StepsFn),
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::Neon4 => (4, crate::neon::steps as StepsFn),
#[cfg(all(target_arch = "aarch64", not(feature = "scalar")))]
Kernel::NeonSha2x4 => (4, crate::neon_sha2::steps4 as StepsFn),
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::ShaNiX4 => (4, crate::shani::steps4 as StepsFn),
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx2_8 => (8, crate::avx2::steps as StepsFn),
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx512_16 => (16, crate::avx512::steps as StepsFn),
#[cfg(all(target_arch = "x86_64", not(feature = "scalar")))]
Kernel::Avx512_16x2 => {
if n <= 16 {
(16, crate::avx512::steps as StepsFn)
} else {
(32, crate::avx512x2::steps2 as StepsFn)
}
}
#[cfg(all(
target_arch = "wasm32",
target_feature = "simd128",
not(feature = "scalar")
))]
Kernel::Simd128_4 => (4, crate::simd128::steps as StepsFn),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn folded_schedule() {
let mut x = 0x243f_6a88_85a3_08d3u64;
let mut rand = move || {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x as u32
};
for _ in 0..100 {
let h = [(); 8].map(|_| Scalar::<1>([rand()]));
let mut w = [Scalar::<1>([0]); 16];
w[..8].copy_from_slice(&h);
for (wj, p) in w[8..].iter_mut().zip(PAD) {
*wj = Scalar::splat(p);
}
let mut want = H0.map(Scalar::<1>::splat);
compress(&mut want, w);
let mut got = H0.map(Scalar::<1>::splat);
compress_chain(&mut got, &h);
assert_eq!(want.map(|s| s.0), got.map(|s| s.0));
}
}
#[test]
fn scheduler_refill() {
for n in [1usize, 2, 5, 8, 9, 17, 33] {
let seeds: Vec<[u8; 32]> = (0..n).map(|i| [i as u8; 32]).collect();
let lens: Vec<u64> = (0..n).map(|i| (i as u64 * 3) % 7).collect();
let mut out = vec![[0u8; 32]; n];
unsafe { run_scheduled(8, steps_scalar8, &seeds, &lens, &mut out) };
for i in 0..n {
assert_eq!(out[i], hash_chain(&seeds[i], lens[i]), "chain {i}");
}
}
}
}