use core::arch::aarch64::{
uint8x16_t, uint64x2_t, vaddq_u64, vdupq_n_u64, veorq_u64, vextq_u64, vget_high_u32,
vget_low_u32, vld1q_u8, vld1q_u64, vmlal_high_u32, vmlal_u32, vmovn_u64, vmull_high_u32,
vmull_u32, vqtbl1q_u8, vreinterpretq_u8_u64, vreinterpretq_u32_u64, vreinterpretq_u64_u8,
vreinterpretq_u64_u32, vrev64q_u32, vshlq_n_u64, vshrq_n_u64, vsriq_n_u64, vst1q_u64,
vuzp1q_u32,
};
use core::mem::MaybeUninit;
use crate::block::{Block, Instance, Position};
use crate::params::{ADDRESSES_IN_BLOCK, OWORDS_IN_BLOCK};
const ADDRESSES_IN_BLOCK_U32: u32 = ADDRESSES_IN_BLOCK as u32;
const SHIFT_ROTATES: bool = false;
const TABLE_ROTATES: bool = true;
const MUL_UMULL: u8 = 0;
const MUL_UMLAL: u8 = 1;
const MUL_UZP: u8 = 2;
const MUL_UZP_PAIR: u8 = 3;
const MUL_UZP_PAIR_MLAL: u8 = 4;
const MUL_DEFAULT: u8 = MUL_UZP_PAIR;
const ROR32_REV: bool = false;
const ROR32_XAR: bool = true;
const ROR32_DEFAULT: bool = if cfg!(target_feature = "sha3") {
ROR32_XAR
} else {
ROR32_REV
};
const FUSED: u8 = 2;
const FUSED2: u8 = 3;
const FUSED2_PREV: u8 = 4;
const FUSED4: u8 = 5;
const SHAPE_DEFAULT: u8 = FUSED2;
#[inline(always)]
unsafe fn zero() -> uint64x2_t {
unsafe { vdupq_n_u64(0) }
}
#[inline(always)]
unsafe fn f_blamka<const MUL: u8>(x: uint64x2_t, y: uint64x2_t) -> uint64x2_t {
const {
assert!(
MUL == MUL_UMULL
|| MUL == MUL_UMLAL
|| MUL == MUL_UZP
|| MUL == MUL_UZP_PAIR
|| MUL == MUL_UZP_PAIR_MLAL,
"unknown fBlaMka spelling"
)
};
unsafe {
if MUL == MUL_UZP {
let u = vuzp1q_u32(vreinterpretq_u32_u64(x), vreinterpretq_u32_u64(y));
let z = vmull_u32(vget_low_u32(u), vget_high_u32(u));
vaddq_u64(vaddq_u64(x, y), vaddq_u64(z, z))
} else {
let lx = vmovn_u64(x);
let ly = vmovn_u64(y);
if MUL == MUL_UMLAL {
let s = vaddq_u64(x, y);
vmlal_u32(vmlal_u32(s, lx, ly), lx, ly)
} else {
let z = vmull_u32(lx, ly);
vaddq_u64(vaddq_u64(x, y), vaddq_u64(z, z))
}
}
}
}
#[inline(always)]
unsafe fn f_blamka2<const MUL: u8>(
x0: uint64x2_t,
y0: uint64x2_t,
x1: uint64x2_t,
y1: uint64x2_t,
) -> (uint64x2_t, uint64x2_t) {
unsafe {
if MUL == MUL_UZP_PAIR || MUL == MUL_UZP_PAIR_MLAL {
let ux = vuzp1q_u32(vreinterpretq_u32_u64(x0), vreinterpretq_u32_u64(x1));
let uy = vuzp1q_u32(vreinterpretq_u32_u64(y0), vreinterpretq_u32_u64(y1));
if MUL == MUL_UZP_PAIR_MLAL {
let lx = vget_low_u32(ux);
let ly = vget_low_u32(uy);
let s0 = vaddq_u64(x0, y0);
let s1 = vaddq_u64(x1, y1);
(
vmlal_u32(vmlal_u32(s0, lx, ly), lx, ly),
vmlal_high_u32(vmlal_high_u32(s1, ux, uy), ux, uy),
)
} else {
let z0 = vmull_u32(vget_low_u32(ux), vget_low_u32(uy));
let z1 = vmull_high_u32(ux, uy);
(
vaddq_u64(vaddq_u64(x0, y0), vaddq_u64(z0, z0)),
vaddq_u64(vaddq_u64(x1, y1), vaddq_u64(z1, z1)),
)
}
} else {
(f_blamka::<MUL>(x0, y0), f_blamka::<MUL>(x1, y1))
}
}
}
#[inline(always)]
unsafe fn ror32<const XAR: bool>(x: uint64x2_t) -> uint64x2_t {
if XAR {
unsafe { vsriq_n_u64::<32>(vshlq_n_u64::<32>(x), x) }
} else {
unsafe { vreinterpretq_u64_u32(vrev64q_u32(vreinterpretq_u32_u64(x))) }
}
}
#[inline(always)]
unsafe fn r24_table() -> uint8x16_t {
const T: [u8; 16] = [3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10];
unsafe { vld1q_u8(T.as_ptr()) }
}
#[inline(always)]
unsafe fn r16_table() -> uint8x16_t {
const T: [u8; 16] = [2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9];
unsafe { vld1q_u8(T.as_ptr()) }
}
#[inline(always)]
unsafe fn ror24<const TBL: bool>(x: uint64x2_t) -> uint64x2_t {
if TBL {
unsafe { vreinterpretq_u64_u8(vqtbl1q_u8(vreinterpretq_u8_u64(x), r24_table())) }
} else {
unsafe { vsriq_n_u64::<24>(vshlq_n_u64::<40>(x), x) }
}
}
#[inline(always)]
unsafe fn ror16<const TBL: bool>(x: uint64x2_t) -> uint64x2_t {
if TBL {
unsafe { vreinterpretq_u64_u8(vqtbl1q_u8(vreinterpretq_u8_u64(x), r16_table())) }
} else {
unsafe { vsriq_n_u64::<16>(vshlq_n_u64::<48>(x), x) }
}
}
#[inline(always)]
unsafe fn ror63(x: uint64x2_t) -> uint64x2_t {
unsafe { veorq_u64(vshrq_n_u64::<63>(x), vaddq_u64(x, x)) }
}
#[inline(always)]
unsafe fn alignr8(hi: uint64x2_t, lo: uint64x2_t) -> uint64x2_t {
unsafe { vextq_u64::<1>(lo, hi) }
}
macro_rules! g1 {
($tbl:ident, $ml:ident, $x32:ident,
$a0:ident, $b0:ident, $c0:ident, $d0:ident,
$a1:ident, $b1:ident, $c1:ident, $d1:ident) => {{
($a0, $a1) = f_blamka2::<$ml>($a0, $b0, $a1, $b1);
$d0 = veorq_u64($d0, $a0);
$d1 = veorq_u64($d1, $a1);
$d0 = ror32::<$x32>($d0);
$d1 = ror32::<$x32>($d1);
($c0, $c1) = f_blamka2::<$ml>($c0, $d0, $c1, $d1);
$b0 = veorq_u64($b0, $c0);
$b1 = veorq_u64($b1, $c1);
$b0 = ror24::<$tbl>($b0);
$b1 = ror24::<$tbl>($b1);
}};
}
macro_rules! g2 {
($tbl:ident, $ml:ident,
$a0:ident, $b0:ident, $c0:ident, $d0:ident,
$a1:ident, $b1:ident, $c1:ident, $d1:ident) => {{
($a0, $a1) = f_blamka2::<$ml>($a0, $b0, $a1, $b1);
$d0 = veorq_u64($d0, $a0);
$d1 = veorq_u64($d1, $a1);
$d0 = ror16::<$tbl>($d0);
$d1 = ror16::<$tbl>($d1);
($c0, $c1) = f_blamka2::<$ml>($c0, $d0, $c1, $d1);
$b0 = veorq_u64($b0, $c0);
$b1 = veorq_u64($b1, $c1);
$b0 = ror63($b0);
$b1 = ror63($b1);
}};
}
macro_rules! diagonalize {
($a0:ident, $b0:ident, $c0:ident, $d0:ident,
$a1:ident, $b1:ident, $c1:ident, $d1:ident) => {{
let t0 = alignr8($b1, $b0);
let t1 = alignr8($b0, $b1);
$b0 = t0;
$b1 = t1;
core::mem::swap(&mut $c0, &mut $c1);
let t0 = alignr8($d1, $d0);
let t1 = alignr8($d0, $d1);
$d0 = t1;
$d1 = t0;
}};
}
macro_rules! undiagonalize {
($a0:ident, $b0:ident, $c0:ident, $d0:ident,
$a1:ident, $b1:ident, $c1:ident, $d1:ident) => {{
let t0 = alignr8($b0, $b1);
let t1 = alignr8($b1, $b0);
$b0 = t0;
$b1 = t1;
core::mem::swap(&mut $c0, &mut $c1);
let t0 = alignr8($d0, $d1);
let t1 = alignr8($d1, $d0);
$d0 = t1;
$d1 = t0;
}};
}
macro_rules! round8 {
($tbl:ident, $ml:ident, $x32:ident,
$a0:ident, $a1:ident, $b0:ident, $b1:ident,
$c0:ident, $c1:ident, $d0:ident, $d1:ident) => {{
g1!($tbl, $ml, $x32, $a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
g2!($tbl, $ml, $a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
diagonalize!($a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
g1!($tbl, $ml, $x32, $a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
g2!($tbl, $ml, $a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
undiagonalize!($a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
}};
}
macro_rules! col_group {
($tbl:ident, $ml:ident, $x32:ident,
$s:expr, $xy:expr, $refp:expr, $nextp:expr, $with_xor:expr,
$i0:expr, $i1:expr, $i2:expr, $i3:expr, $i4:expr, $i5:expr, $i6:expr, $i7:expr) => {{
let mut a0 = veorq_u64($s[$i0], vld1q_u64($refp.add(2 * $i0)));
let mut a1 = veorq_u64($s[$i1], vld1q_u64($refp.add(2 * $i1)));
let mut b0 = veorq_u64($s[$i2], vld1q_u64($refp.add(2 * $i2)));
let mut b1 = veorq_u64($s[$i3], vld1q_u64($refp.add(2 * $i3)));
let mut c0 = veorq_u64($s[$i4], vld1q_u64($refp.add(2 * $i4)));
let mut c1 = veorq_u64($s[$i5], vld1q_u64($refp.add(2 * $i5)));
let mut d0 = veorq_u64($s[$i6], vld1q_u64($refp.add(2 * $i6)));
let mut d1 = veorq_u64($s[$i7], vld1q_u64($refp.add(2 * $i7)));
if $with_xor {
$xy[$i0] = MaybeUninit::new(veorq_u64(a0, vld1q_u64($nextp.add(2 * $i0))));
$xy[$i1] = MaybeUninit::new(veorq_u64(a1, vld1q_u64($nextp.add(2 * $i1))));
$xy[$i2] = MaybeUninit::new(veorq_u64(b0, vld1q_u64($nextp.add(2 * $i2))));
$xy[$i3] = MaybeUninit::new(veorq_u64(b1, vld1q_u64($nextp.add(2 * $i3))));
$xy[$i4] = MaybeUninit::new(veorq_u64(c0, vld1q_u64($nextp.add(2 * $i4))));
$xy[$i5] = MaybeUninit::new(veorq_u64(c1, vld1q_u64($nextp.add(2 * $i5))));
$xy[$i6] = MaybeUninit::new(veorq_u64(d0, vld1q_u64($nextp.add(2 * $i6))));
$xy[$i7] = MaybeUninit::new(veorq_u64(d1, vld1q_u64($nextp.add(2 * $i7))));
} else {
$xy[$i0] = MaybeUninit::new(a0);
$xy[$i1] = MaybeUninit::new(a1);
$xy[$i2] = MaybeUninit::new(b0);
$xy[$i3] = MaybeUninit::new(b1);
$xy[$i4] = MaybeUninit::new(c0);
$xy[$i5] = MaybeUninit::new(c1);
$xy[$i6] = MaybeUninit::new(d0);
$xy[$i7] = MaybeUninit::new(d1);
}
round8!($tbl, $ml, $x32, a0, a1, b0, b1, c0, c1, d0, d1);
$s[$i0] = a0;
$s[$i1] = a1;
$s[$i2] = b0;
$s[$i3] = b1;
$s[$i4] = c0;
$s[$i5] = c1;
$s[$i6] = d0;
$s[$i7] = d1;
}};
}
macro_rules! row_group {
($tbl:ident, $ml:ident, $x32:ident, $s:expr, $xy:expr, $nextp:expr,
$i0:expr, $i1:expr, $i2:expr, $i3:expr, $i4:expr, $i5:expr, $i6:expr, $i7:expr) => {{
let mut a0 = $s[$i0];
let mut a1 = $s[$i1];
let mut b0 = $s[$i2];
let mut b1 = $s[$i3];
let mut c0 = $s[$i4];
let mut c1 = $s[$i5];
let mut d0 = $s[$i6];
let mut d1 = $s[$i7];
round8!($tbl, $ml, $x32, a0, a1, b0, b1, c0, c1, d0, d1);
a0 = veorq_u64(a0, $xy[$i0].assume_init());
a1 = veorq_u64(a1, $xy[$i1].assume_init());
b0 = veorq_u64(b0, $xy[$i2].assume_init());
b1 = veorq_u64(b1, $xy[$i3].assume_init());
c0 = veorq_u64(c0, $xy[$i4].assume_init());
c1 = veorq_u64(c1, $xy[$i5].assume_init());
d0 = veorq_u64(d0, $xy[$i6].assume_init());
d1 = veorq_u64(d1, $xy[$i7].assume_init());
$s[$i0] = a0;
$s[$i1] = a1;
$s[$i2] = b0;
$s[$i3] = b1;
$s[$i4] = c0;
$s[$i5] = c1;
$s[$i6] = d0;
$s[$i7] = d1;
vst1q_u64($nextp.add(2 * $i0), a0);
vst1q_u64($nextp.add(2 * $i1), a1);
vst1q_u64($nextp.add(2 * $i2), b0);
vst1q_u64($nextp.add(2 * $i3), b1);
vst1q_u64($nextp.add(2 * $i4), c0);
vst1q_u64($nextp.add(2 * $i5), c1);
vst1q_u64($nextp.add(2 * $i6), d0);
vst1q_u64($nextp.add(2 * $i7), d1);
}};
}
macro_rules! round8x2 {
($tbl:ident, $ml:ident, $x32:ident,
$a0:ident, $a1:ident, $b0:ident, $b1:ident, $c0:ident, $c1:ident, $d0:ident, $d1:ident, $e0:ident, $e1:ident, $f0:ident, $f1:ident, $g0:ident, $g1v:ident, $h0:ident, $h1:ident) => {{
g1!($tbl, $ml, $x32, $a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
g1!($tbl, $ml, $x32, $e0, $f0, $g0, $h0, $e1, $f1, $g1v, $h1);
g2!($tbl, $ml, $a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
g2!($tbl, $ml, $e0, $f0, $g0, $h0, $e1, $f1, $g1v, $h1);
diagonalize!($a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
diagonalize!($e0, $f0, $g0, $h0, $e1, $f1, $g1v, $h1);
g1!($tbl, $ml, $x32, $a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
g1!($tbl, $ml, $x32, $e0, $f0, $g0, $h0, $e1, $f1, $g1v, $h1);
g2!($tbl, $ml, $a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
g2!($tbl, $ml, $e0, $f0, $g0, $h0, $e1, $f1, $g1v, $h1);
undiagonalize!($a0, $b0, $c0, $d0, $a1, $b1, $c1, $d1);
undiagonalize!($e0, $f0, $g0, $h0, $e1, $f1, $g1v, $h1);
}};
}
macro_rules! col_group2 {
($tbl:ident, $ml:ident, $x32:ident, $carry:expr, $prevp:expr,
$s:expr, $xy:expr, $refp:expr, $nextp:expr, $with_xor:expr,
$i0:expr, $i1:expr, $i2:expr, $i3:expr, $i4:expr, $i5:expr, $i6:expr, $i7:expr, $i8:expr, $i9:expr, $i10:expr, $i11:expr, $i12:expr, $i13:expr, $i14:expr, $i15:expr) => {{
let mut a0 = veorq_u64(
if $carry {
$s[$i0]
} else {
vld1q_u64($prevp.add(2 * $i0))
},
vld1q_u64($refp.add(2 * $i0)),
);
let mut a1 = veorq_u64(
if $carry {
$s[$i1]
} else {
vld1q_u64($prevp.add(2 * $i1))
},
vld1q_u64($refp.add(2 * $i1)),
);
let mut b0 = veorq_u64(
if $carry {
$s[$i2]
} else {
vld1q_u64($prevp.add(2 * $i2))
},
vld1q_u64($refp.add(2 * $i2)),
);
let mut b1 = veorq_u64(
if $carry {
$s[$i3]
} else {
vld1q_u64($prevp.add(2 * $i3))
},
vld1q_u64($refp.add(2 * $i3)),
);
let mut c0 = veorq_u64(
if $carry {
$s[$i4]
} else {
vld1q_u64($prevp.add(2 * $i4))
},
vld1q_u64($refp.add(2 * $i4)),
);
let mut c1 = veorq_u64(
if $carry {
$s[$i5]
} else {
vld1q_u64($prevp.add(2 * $i5))
},
vld1q_u64($refp.add(2 * $i5)),
);
let mut d0 = veorq_u64(
if $carry {
$s[$i6]
} else {
vld1q_u64($prevp.add(2 * $i6))
},
vld1q_u64($refp.add(2 * $i6)),
);
let mut d1 = veorq_u64(
if $carry {
$s[$i7]
} else {
vld1q_u64($prevp.add(2 * $i7))
},
vld1q_u64($refp.add(2 * $i7)),
);
let mut e0 = veorq_u64(
if $carry {
$s[$i8]
} else {
vld1q_u64($prevp.add(2 * $i8))
},
vld1q_u64($refp.add(2 * $i8)),
);
let mut e1 = veorq_u64(
if $carry {
$s[$i9]
} else {
vld1q_u64($prevp.add(2 * $i9))
},
vld1q_u64($refp.add(2 * $i9)),
);
let mut f0 = veorq_u64(
if $carry {
$s[$i10]
} else {
vld1q_u64($prevp.add(2 * $i10))
},
vld1q_u64($refp.add(2 * $i10)),
);
let mut f1 = veorq_u64(
if $carry {
$s[$i11]
} else {
vld1q_u64($prevp.add(2 * $i11))
},
vld1q_u64($refp.add(2 * $i11)),
);
let mut g0 = veorq_u64(
if $carry {
$s[$i12]
} else {
vld1q_u64($prevp.add(2 * $i12))
},
vld1q_u64($refp.add(2 * $i12)),
);
let mut g1v = veorq_u64(
if $carry {
$s[$i13]
} else {
vld1q_u64($prevp.add(2 * $i13))
},
vld1q_u64($refp.add(2 * $i13)),
);
let mut h0 = veorq_u64(
if $carry {
$s[$i14]
} else {
vld1q_u64($prevp.add(2 * $i14))
},
vld1q_u64($refp.add(2 * $i14)),
);
let mut h1 = veorq_u64(
if $carry {
$s[$i15]
} else {
vld1q_u64($prevp.add(2 * $i15))
},
vld1q_u64($refp.add(2 * $i15)),
);
if $with_xor {
$xy[$i0] = MaybeUninit::new(veorq_u64(a0, vld1q_u64($nextp.add(2 * $i0))));
$xy[$i1] = MaybeUninit::new(veorq_u64(a1, vld1q_u64($nextp.add(2 * $i1))));
$xy[$i2] = MaybeUninit::new(veorq_u64(b0, vld1q_u64($nextp.add(2 * $i2))));
$xy[$i3] = MaybeUninit::new(veorq_u64(b1, vld1q_u64($nextp.add(2 * $i3))));
$xy[$i4] = MaybeUninit::new(veorq_u64(c0, vld1q_u64($nextp.add(2 * $i4))));
$xy[$i5] = MaybeUninit::new(veorq_u64(c1, vld1q_u64($nextp.add(2 * $i5))));
$xy[$i6] = MaybeUninit::new(veorq_u64(d0, vld1q_u64($nextp.add(2 * $i6))));
$xy[$i7] = MaybeUninit::new(veorq_u64(d1, vld1q_u64($nextp.add(2 * $i7))));
$xy[$i8] = MaybeUninit::new(veorq_u64(e0, vld1q_u64($nextp.add(2 * $i8))));
$xy[$i9] = MaybeUninit::new(veorq_u64(e1, vld1q_u64($nextp.add(2 * $i9))));
$xy[$i10] = MaybeUninit::new(veorq_u64(f0, vld1q_u64($nextp.add(2 * $i10))));
$xy[$i11] = MaybeUninit::new(veorq_u64(f1, vld1q_u64($nextp.add(2 * $i11))));
$xy[$i12] = MaybeUninit::new(veorq_u64(g0, vld1q_u64($nextp.add(2 * $i12))));
$xy[$i13] = MaybeUninit::new(veorq_u64(g1v, vld1q_u64($nextp.add(2 * $i13))));
$xy[$i14] = MaybeUninit::new(veorq_u64(h0, vld1q_u64($nextp.add(2 * $i14))));
$xy[$i15] = MaybeUninit::new(veorq_u64(h1, vld1q_u64($nextp.add(2 * $i15))));
} else {
$xy[$i0] = MaybeUninit::new(a0);
$xy[$i1] = MaybeUninit::new(a1);
$xy[$i2] = MaybeUninit::new(b0);
$xy[$i3] = MaybeUninit::new(b1);
$xy[$i4] = MaybeUninit::new(c0);
$xy[$i5] = MaybeUninit::new(c1);
$xy[$i6] = MaybeUninit::new(d0);
$xy[$i7] = MaybeUninit::new(d1);
$xy[$i8] = MaybeUninit::new(e0);
$xy[$i9] = MaybeUninit::new(e1);
$xy[$i10] = MaybeUninit::new(f0);
$xy[$i11] = MaybeUninit::new(f1);
$xy[$i12] = MaybeUninit::new(g0);
$xy[$i13] = MaybeUninit::new(g1v);
$xy[$i14] = MaybeUninit::new(h0);
$xy[$i15] = MaybeUninit::new(h1);
}
round8x2!(
$tbl, $ml, $x32, a0, a1, b0, b1, c0, c1, d0, d1, e0, e1, f0, f1, g0, g1v, h0, h1
);
$s[$i0] = a0;
$s[$i1] = a1;
$s[$i2] = b0;
$s[$i3] = b1;
$s[$i4] = c0;
$s[$i5] = c1;
$s[$i6] = d0;
$s[$i7] = d1;
$s[$i8] = e0;
$s[$i9] = e1;
$s[$i10] = f0;
$s[$i11] = f1;
$s[$i12] = g0;
$s[$i13] = g1v;
$s[$i14] = h0;
$s[$i15] = h1;
}};
}
macro_rules! row_group2 {
($tbl:ident, $ml:ident, $x32:ident, $carry:expr, $s:expr, $xy:expr, $nextp:expr,
$i0:expr, $i1:expr, $i2:expr, $i3:expr, $i4:expr, $i5:expr, $i6:expr, $i7:expr, $i8:expr, $i9:expr, $i10:expr, $i11:expr, $i12:expr, $i13:expr, $i14:expr, $i15:expr) => {{
let mut a0 = $s[$i0];
let mut a1 = $s[$i1];
let mut b0 = $s[$i2];
let mut b1 = $s[$i3];
let mut c0 = $s[$i4];
let mut c1 = $s[$i5];
let mut d0 = $s[$i6];
let mut d1 = $s[$i7];
let mut e0 = $s[$i8];
let mut e1 = $s[$i9];
let mut f0 = $s[$i10];
let mut f1 = $s[$i11];
let mut g0 = $s[$i12];
let mut g1v = $s[$i13];
let mut h0 = $s[$i14];
let mut h1 = $s[$i15];
round8x2!(
$tbl, $ml, $x32, a0, a1, b0, b1, c0, c1, d0, d1, e0, e1, f0, f1, g0, g1v, h0, h1
);
a0 = veorq_u64(a0, $xy[$i0].assume_init());
a1 = veorq_u64(a1, $xy[$i1].assume_init());
b0 = veorq_u64(b0, $xy[$i2].assume_init());
b1 = veorq_u64(b1, $xy[$i3].assume_init());
c0 = veorq_u64(c0, $xy[$i4].assume_init());
c1 = veorq_u64(c1, $xy[$i5].assume_init());
d0 = veorq_u64(d0, $xy[$i6].assume_init());
d1 = veorq_u64(d1, $xy[$i7].assume_init());
e0 = veorq_u64(e0, $xy[$i8].assume_init());
e1 = veorq_u64(e1, $xy[$i9].assume_init());
f0 = veorq_u64(f0, $xy[$i10].assume_init());
f1 = veorq_u64(f1, $xy[$i11].assume_init());
g0 = veorq_u64(g0, $xy[$i12].assume_init());
g1v = veorq_u64(g1v, $xy[$i13].assume_init());
h0 = veorq_u64(h0, $xy[$i14].assume_init());
h1 = veorq_u64(h1, $xy[$i15].assume_init());
if $carry {
$s[$i0] = a0;
$s[$i1] = a1;
$s[$i2] = b0;
$s[$i3] = b1;
$s[$i4] = c0;
$s[$i5] = c1;
$s[$i6] = d0;
$s[$i7] = d1;
$s[$i8] = e0;
$s[$i9] = e1;
$s[$i10] = f0;
$s[$i11] = f1;
$s[$i12] = g0;
$s[$i13] = g1v;
$s[$i14] = h0;
$s[$i15] = h1;
}
vst1q_u64($nextp.add(2 * $i0), a0);
vst1q_u64($nextp.add(2 * $i1), a1);
vst1q_u64($nextp.add(2 * $i2), b0);
vst1q_u64($nextp.add(2 * $i3), b1);
vst1q_u64($nextp.add(2 * $i4), c0);
vst1q_u64($nextp.add(2 * $i5), c1);
vst1q_u64($nextp.add(2 * $i6), d0);
vst1q_u64($nextp.add(2 * $i7), d1);
vst1q_u64($nextp.add(2 * $i8), e0);
vst1q_u64($nextp.add(2 * $i9), e1);
vst1q_u64($nextp.add(2 * $i10), f0);
vst1q_u64($nextp.add(2 * $i11), f1);
vst1q_u64($nextp.add(2 * $i12), g0);
vst1q_u64($nextp.add(2 * $i13), g1v);
vst1q_u64($nextp.add(2 * $i14), h0);
vst1q_u64($nextp.add(2 * $i15), h1);
}};
}
macro_rules! round8x4 {
($tbl:ident, $ml:ident, $x32:ident,
$a00:ident, $a01:ident, $b00:ident, $b01:ident, $c00:ident, $c01:ident, $d00:ident, $d01:ident, $a10:ident, $a11:ident, $b10:ident, $b11:ident, $c10:ident, $c11:ident, $d10:ident, $d11:ident, $a20:ident, $a21:ident, $b20:ident, $b21:ident, $c20:ident, $c21:ident, $d20:ident, $d21:ident, $a30:ident, $a31:ident, $b30:ident, $b31:ident, $c30:ident, $c31:ident, $d30:ident, $d31:ident) => {{
g1!(
$tbl, $ml, $x32, $a00, $b00, $c00, $d00, $a01, $b01, $c01, $d01
);
g1!(
$tbl, $ml, $x32, $a10, $b10, $c10, $d10, $a11, $b11, $c11, $d11
);
g1!(
$tbl, $ml, $x32, $a20, $b20, $c20, $d20, $a21, $b21, $c21, $d21
);
g1!(
$tbl, $ml, $x32, $a30, $b30, $c30, $d30, $a31, $b31, $c31, $d31
);
g2!($tbl, $ml, $a00, $b00, $c00, $d00, $a01, $b01, $c01, $d01);
g2!($tbl, $ml, $a10, $b10, $c10, $d10, $a11, $b11, $c11, $d11);
g2!($tbl, $ml, $a20, $b20, $c20, $d20, $a21, $b21, $c21, $d21);
g2!($tbl, $ml, $a30, $b30, $c30, $d30, $a31, $b31, $c31, $d31);
diagonalize!($a00, $b00, $c00, $d00, $a01, $b01, $c01, $d01);
diagonalize!($a10, $b10, $c10, $d10, $a11, $b11, $c11, $d11);
diagonalize!($a20, $b20, $c20, $d20, $a21, $b21, $c21, $d21);
diagonalize!($a30, $b30, $c30, $d30, $a31, $b31, $c31, $d31);
g1!(
$tbl, $ml, $x32, $a00, $b00, $c00, $d00, $a01, $b01, $c01, $d01
);
g1!(
$tbl, $ml, $x32, $a10, $b10, $c10, $d10, $a11, $b11, $c11, $d11
);
g1!(
$tbl, $ml, $x32, $a20, $b20, $c20, $d20, $a21, $b21, $c21, $d21
);
g1!(
$tbl, $ml, $x32, $a30, $b30, $c30, $d30, $a31, $b31, $c31, $d31
);
g2!($tbl, $ml, $a00, $b00, $c00, $d00, $a01, $b01, $c01, $d01);
g2!($tbl, $ml, $a10, $b10, $c10, $d10, $a11, $b11, $c11, $d11);
g2!($tbl, $ml, $a20, $b20, $c20, $d20, $a21, $b21, $c21, $d21);
g2!($tbl, $ml, $a30, $b30, $c30, $d30, $a31, $b31, $c31, $d31);
undiagonalize!($a00, $b00, $c00, $d00, $a01, $b01, $c01, $d01);
undiagonalize!($a10, $b10, $c10, $d10, $a11, $b11, $c11, $d11);
undiagonalize!($a20, $b20, $c20, $d20, $a21, $b21, $c21, $d21);
undiagonalize!($a30, $b30, $c30, $d30, $a31, $b31, $c31, $d31);
}};
}
macro_rules! col_group4 {
($tbl:ident, $ml:ident, $x32:ident,
$s:expr, $xy:expr, $refp:expr, $nextp:expr, $with_xor:expr,
$i0:expr, $i1:expr, $i2:expr, $i3:expr, $i4:expr, $i5:expr, $i6:expr, $i7:expr, $i8:expr, $i9:expr, $i10:expr, $i11:expr, $i12:expr, $i13:expr, $i14:expr, $i15:expr, $i16:expr, $i17:expr, $i18:expr, $i19:expr, $i20:expr, $i21:expr, $i22:expr, $i23:expr, $i24:expr, $i25:expr, $i26:expr, $i27:expr, $i28:expr, $i29:expr, $i30:expr, $i31:expr) => {{
let mut a00 = veorq_u64($s[$i0], vld1q_u64($refp.add(2 * $i0)));
let mut a01 = veorq_u64($s[$i1], vld1q_u64($refp.add(2 * $i1)));
let mut b00 = veorq_u64($s[$i2], vld1q_u64($refp.add(2 * $i2)));
let mut b01 = veorq_u64($s[$i3], vld1q_u64($refp.add(2 * $i3)));
let mut c00 = veorq_u64($s[$i4], vld1q_u64($refp.add(2 * $i4)));
let mut c01 = veorq_u64($s[$i5], vld1q_u64($refp.add(2 * $i5)));
let mut d00 = veorq_u64($s[$i6], vld1q_u64($refp.add(2 * $i6)));
let mut d01 = veorq_u64($s[$i7], vld1q_u64($refp.add(2 * $i7)));
let mut a10 = veorq_u64($s[$i8], vld1q_u64($refp.add(2 * $i8)));
let mut a11 = veorq_u64($s[$i9], vld1q_u64($refp.add(2 * $i9)));
let mut b10 = veorq_u64($s[$i10], vld1q_u64($refp.add(2 * $i10)));
let mut b11 = veorq_u64($s[$i11], vld1q_u64($refp.add(2 * $i11)));
let mut c10 = veorq_u64($s[$i12], vld1q_u64($refp.add(2 * $i12)));
let mut c11 = veorq_u64($s[$i13], vld1q_u64($refp.add(2 * $i13)));
let mut d10 = veorq_u64($s[$i14], vld1q_u64($refp.add(2 * $i14)));
let mut d11 = veorq_u64($s[$i15], vld1q_u64($refp.add(2 * $i15)));
let mut a20 = veorq_u64($s[$i16], vld1q_u64($refp.add(2 * $i16)));
let mut a21 = veorq_u64($s[$i17], vld1q_u64($refp.add(2 * $i17)));
let mut b20 = veorq_u64($s[$i18], vld1q_u64($refp.add(2 * $i18)));
let mut b21 = veorq_u64($s[$i19], vld1q_u64($refp.add(2 * $i19)));
let mut c20 = veorq_u64($s[$i20], vld1q_u64($refp.add(2 * $i20)));
let mut c21 = veorq_u64($s[$i21], vld1q_u64($refp.add(2 * $i21)));
let mut d20 = veorq_u64($s[$i22], vld1q_u64($refp.add(2 * $i22)));
let mut d21 = veorq_u64($s[$i23], vld1q_u64($refp.add(2 * $i23)));
let mut a30 = veorq_u64($s[$i24], vld1q_u64($refp.add(2 * $i24)));
let mut a31 = veorq_u64($s[$i25], vld1q_u64($refp.add(2 * $i25)));
let mut b30 = veorq_u64($s[$i26], vld1q_u64($refp.add(2 * $i26)));
let mut b31 = veorq_u64($s[$i27], vld1q_u64($refp.add(2 * $i27)));
let mut c30 = veorq_u64($s[$i28], vld1q_u64($refp.add(2 * $i28)));
let mut c31 = veorq_u64($s[$i29], vld1q_u64($refp.add(2 * $i29)));
let mut d30 = veorq_u64($s[$i30], vld1q_u64($refp.add(2 * $i30)));
let mut d31 = veorq_u64($s[$i31], vld1q_u64($refp.add(2 * $i31)));
if $with_xor {
$xy[$i0] = MaybeUninit::new(veorq_u64(a00, vld1q_u64($nextp.add(2 * $i0))));
$xy[$i1] = MaybeUninit::new(veorq_u64(a01, vld1q_u64($nextp.add(2 * $i1))));
$xy[$i2] = MaybeUninit::new(veorq_u64(b00, vld1q_u64($nextp.add(2 * $i2))));
$xy[$i3] = MaybeUninit::new(veorq_u64(b01, vld1q_u64($nextp.add(2 * $i3))));
$xy[$i4] = MaybeUninit::new(veorq_u64(c00, vld1q_u64($nextp.add(2 * $i4))));
$xy[$i5] = MaybeUninit::new(veorq_u64(c01, vld1q_u64($nextp.add(2 * $i5))));
$xy[$i6] = MaybeUninit::new(veorq_u64(d00, vld1q_u64($nextp.add(2 * $i6))));
$xy[$i7] = MaybeUninit::new(veorq_u64(d01, vld1q_u64($nextp.add(2 * $i7))));
$xy[$i8] = MaybeUninit::new(veorq_u64(a10, vld1q_u64($nextp.add(2 * $i8))));
$xy[$i9] = MaybeUninit::new(veorq_u64(a11, vld1q_u64($nextp.add(2 * $i9))));
$xy[$i10] = MaybeUninit::new(veorq_u64(b10, vld1q_u64($nextp.add(2 * $i10))));
$xy[$i11] = MaybeUninit::new(veorq_u64(b11, vld1q_u64($nextp.add(2 * $i11))));
$xy[$i12] = MaybeUninit::new(veorq_u64(c10, vld1q_u64($nextp.add(2 * $i12))));
$xy[$i13] = MaybeUninit::new(veorq_u64(c11, vld1q_u64($nextp.add(2 * $i13))));
$xy[$i14] = MaybeUninit::new(veorq_u64(d10, vld1q_u64($nextp.add(2 * $i14))));
$xy[$i15] = MaybeUninit::new(veorq_u64(d11, vld1q_u64($nextp.add(2 * $i15))));
$xy[$i16] = MaybeUninit::new(veorq_u64(a20, vld1q_u64($nextp.add(2 * $i16))));
$xy[$i17] = MaybeUninit::new(veorq_u64(a21, vld1q_u64($nextp.add(2 * $i17))));
$xy[$i18] = MaybeUninit::new(veorq_u64(b20, vld1q_u64($nextp.add(2 * $i18))));
$xy[$i19] = MaybeUninit::new(veorq_u64(b21, vld1q_u64($nextp.add(2 * $i19))));
$xy[$i20] = MaybeUninit::new(veorq_u64(c20, vld1q_u64($nextp.add(2 * $i20))));
$xy[$i21] = MaybeUninit::new(veorq_u64(c21, vld1q_u64($nextp.add(2 * $i21))));
$xy[$i22] = MaybeUninit::new(veorq_u64(d20, vld1q_u64($nextp.add(2 * $i22))));
$xy[$i23] = MaybeUninit::new(veorq_u64(d21, vld1q_u64($nextp.add(2 * $i23))));
$xy[$i24] = MaybeUninit::new(veorq_u64(a30, vld1q_u64($nextp.add(2 * $i24))));
$xy[$i25] = MaybeUninit::new(veorq_u64(a31, vld1q_u64($nextp.add(2 * $i25))));
$xy[$i26] = MaybeUninit::new(veorq_u64(b30, vld1q_u64($nextp.add(2 * $i26))));
$xy[$i27] = MaybeUninit::new(veorq_u64(b31, vld1q_u64($nextp.add(2 * $i27))));
$xy[$i28] = MaybeUninit::new(veorq_u64(c30, vld1q_u64($nextp.add(2 * $i28))));
$xy[$i29] = MaybeUninit::new(veorq_u64(c31, vld1q_u64($nextp.add(2 * $i29))));
$xy[$i30] = MaybeUninit::new(veorq_u64(d30, vld1q_u64($nextp.add(2 * $i30))));
$xy[$i31] = MaybeUninit::new(veorq_u64(d31, vld1q_u64($nextp.add(2 * $i31))));
} else {
$xy[$i0] = MaybeUninit::new(a00);
$xy[$i1] = MaybeUninit::new(a01);
$xy[$i2] = MaybeUninit::new(b00);
$xy[$i3] = MaybeUninit::new(b01);
$xy[$i4] = MaybeUninit::new(c00);
$xy[$i5] = MaybeUninit::new(c01);
$xy[$i6] = MaybeUninit::new(d00);
$xy[$i7] = MaybeUninit::new(d01);
$xy[$i8] = MaybeUninit::new(a10);
$xy[$i9] = MaybeUninit::new(a11);
$xy[$i10] = MaybeUninit::new(b10);
$xy[$i11] = MaybeUninit::new(b11);
$xy[$i12] = MaybeUninit::new(c10);
$xy[$i13] = MaybeUninit::new(c11);
$xy[$i14] = MaybeUninit::new(d10);
$xy[$i15] = MaybeUninit::new(d11);
$xy[$i16] = MaybeUninit::new(a20);
$xy[$i17] = MaybeUninit::new(a21);
$xy[$i18] = MaybeUninit::new(b20);
$xy[$i19] = MaybeUninit::new(b21);
$xy[$i20] = MaybeUninit::new(c20);
$xy[$i21] = MaybeUninit::new(c21);
$xy[$i22] = MaybeUninit::new(d20);
$xy[$i23] = MaybeUninit::new(d21);
$xy[$i24] = MaybeUninit::new(a30);
$xy[$i25] = MaybeUninit::new(a31);
$xy[$i26] = MaybeUninit::new(b30);
$xy[$i27] = MaybeUninit::new(b31);
$xy[$i28] = MaybeUninit::new(c30);
$xy[$i29] = MaybeUninit::new(c31);
$xy[$i30] = MaybeUninit::new(d30);
$xy[$i31] = MaybeUninit::new(d31);
}
round8x4!(
$tbl, $ml, $x32, a00, a01, b00, b01, c00, c01, d00, d01, a10, a11, b10, b11, c10, c11,
d10, d11, a20, a21, b20, b21, c20, c21, d20, d21, a30, a31, b30, b31, c30, c31, d30,
d31
);
$s[$i0] = a00;
$s[$i1] = a01;
$s[$i2] = b00;
$s[$i3] = b01;
$s[$i4] = c00;
$s[$i5] = c01;
$s[$i6] = d00;
$s[$i7] = d01;
$s[$i8] = a10;
$s[$i9] = a11;
$s[$i10] = b10;
$s[$i11] = b11;
$s[$i12] = c10;
$s[$i13] = c11;
$s[$i14] = d10;
$s[$i15] = d11;
$s[$i16] = a20;
$s[$i17] = a21;
$s[$i18] = b20;
$s[$i19] = b21;
$s[$i20] = c20;
$s[$i21] = c21;
$s[$i22] = d20;
$s[$i23] = d21;
$s[$i24] = a30;
$s[$i25] = a31;
$s[$i26] = b30;
$s[$i27] = b31;
$s[$i28] = c30;
$s[$i29] = c31;
$s[$i30] = d30;
$s[$i31] = d31;
}};
}
macro_rules! row_group4 {
($tbl:ident, $ml:ident, $x32:ident, $s:expr, $xy:expr, $nextp:expr,
$i0:expr, $i1:expr, $i2:expr, $i3:expr, $i4:expr, $i5:expr, $i6:expr, $i7:expr, $i8:expr, $i9:expr, $i10:expr, $i11:expr, $i12:expr, $i13:expr, $i14:expr, $i15:expr, $i16:expr, $i17:expr, $i18:expr, $i19:expr, $i20:expr, $i21:expr, $i22:expr, $i23:expr, $i24:expr, $i25:expr, $i26:expr, $i27:expr, $i28:expr, $i29:expr, $i30:expr, $i31:expr) => {{
let mut a00 = $s[$i0];
let mut a01 = $s[$i1];
let mut b00 = $s[$i2];
let mut b01 = $s[$i3];
let mut c00 = $s[$i4];
let mut c01 = $s[$i5];
let mut d00 = $s[$i6];
let mut d01 = $s[$i7];
let mut a10 = $s[$i8];
let mut a11 = $s[$i9];
let mut b10 = $s[$i10];
let mut b11 = $s[$i11];
let mut c10 = $s[$i12];
let mut c11 = $s[$i13];
let mut d10 = $s[$i14];
let mut d11 = $s[$i15];
let mut a20 = $s[$i16];
let mut a21 = $s[$i17];
let mut b20 = $s[$i18];
let mut b21 = $s[$i19];
let mut c20 = $s[$i20];
let mut c21 = $s[$i21];
let mut d20 = $s[$i22];
let mut d21 = $s[$i23];
let mut a30 = $s[$i24];
let mut a31 = $s[$i25];
let mut b30 = $s[$i26];
let mut b31 = $s[$i27];
let mut c30 = $s[$i28];
let mut c31 = $s[$i29];
let mut d30 = $s[$i30];
let mut d31 = $s[$i31];
round8x4!(
$tbl, $ml, $x32, a00, a01, b00, b01, c00, c01, d00, d01, a10, a11, b10, b11, c10, c11,
d10, d11, a20, a21, b20, b21, c20, c21, d20, d21, a30, a31, b30, b31, c30, c31, d30,
d31
);
a00 = veorq_u64(a00, $xy[$i0].assume_init());
a01 = veorq_u64(a01, $xy[$i1].assume_init());
b00 = veorq_u64(b00, $xy[$i2].assume_init());
b01 = veorq_u64(b01, $xy[$i3].assume_init());
c00 = veorq_u64(c00, $xy[$i4].assume_init());
c01 = veorq_u64(c01, $xy[$i5].assume_init());
d00 = veorq_u64(d00, $xy[$i6].assume_init());
d01 = veorq_u64(d01, $xy[$i7].assume_init());
a10 = veorq_u64(a10, $xy[$i8].assume_init());
a11 = veorq_u64(a11, $xy[$i9].assume_init());
b10 = veorq_u64(b10, $xy[$i10].assume_init());
b11 = veorq_u64(b11, $xy[$i11].assume_init());
c10 = veorq_u64(c10, $xy[$i12].assume_init());
c11 = veorq_u64(c11, $xy[$i13].assume_init());
d10 = veorq_u64(d10, $xy[$i14].assume_init());
d11 = veorq_u64(d11, $xy[$i15].assume_init());
a20 = veorq_u64(a20, $xy[$i16].assume_init());
a21 = veorq_u64(a21, $xy[$i17].assume_init());
b20 = veorq_u64(b20, $xy[$i18].assume_init());
b21 = veorq_u64(b21, $xy[$i19].assume_init());
c20 = veorq_u64(c20, $xy[$i20].assume_init());
c21 = veorq_u64(c21, $xy[$i21].assume_init());
d20 = veorq_u64(d20, $xy[$i22].assume_init());
d21 = veorq_u64(d21, $xy[$i23].assume_init());
a30 = veorq_u64(a30, $xy[$i24].assume_init());
a31 = veorq_u64(a31, $xy[$i25].assume_init());
b30 = veorq_u64(b30, $xy[$i26].assume_init());
b31 = veorq_u64(b31, $xy[$i27].assume_init());
c30 = veorq_u64(c30, $xy[$i28].assume_init());
c31 = veorq_u64(c31, $xy[$i29].assume_init());
d30 = veorq_u64(d30, $xy[$i30].assume_init());
d31 = veorq_u64(d31, $xy[$i31].assume_init());
$s[$i0] = a00;
$s[$i1] = a01;
$s[$i2] = b00;
$s[$i3] = b01;
$s[$i4] = c00;
$s[$i5] = c01;
$s[$i6] = d00;
$s[$i7] = d01;
$s[$i8] = a10;
$s[$i9] = a11;
$s[$i10] = b10;
$s[$i11] = b11;
$s[$i12] = c10;
$s[$i13] = c11;
$s[$i14] = d10;
$s[$i15] = d11;
$s[$i16] = a20;
$s[$i17] = a21;
$s[$i18] = b20;
$s[$i19] = b21;
$s[$i20] = c20;
$s[$i21] = c21;
$s[$i22] = d20;
$s[$i23] = d21;
$s[$i24] = a30;
$s[$i25] = a31;
$s[$i26] = b30;
$s[$i27] = b31;
$s[$i28] = c30;
$s[$i29] = c31;
$s[$i30] = d30;
$s[$i31] = d31;
vst1q_u64($nextp.add(2 * $i0), a00);
vst1q_u64($nextp.add(2 * $i1), a01);
vst1q_u64($nextp.add(2 * $i2), b00);
vst1q_u64($nextp.add(2 * $i3), b01);
vst1q_u64($nextp.add(2 * $i4), c00);
vst1q_u64($nextp.add(2 * $i5), c01);
vst1q_u64($nextp.add(2 * $i6), d00);
vst1q_u64($nextp.add(2 * $i7), d01);
vst1q_u64($nextp.add(2 * $i8), a10);
vst1q_u64($nextp.add(2 * $i9), a11);
vst1q_u64($nextp.add(2 * $i10), b10);
vst1q_u64($nextp.add(2 * $i11), b11);
vst1q_u64($nextp.add(2 * $i12), c10);
vst1q_u64($nextp.add(2 * $i13), c11);
vst1q_u64($nextp.add(2 * $i14), d10);
vst1q_u64($nextp.add(2 * $i15), d11);
vst1q_u64($nextp.add(2 * $i16), a20);
vst1q_u64($nextp.add(2 * $i17), a21);
vst1q_u64($nextp.add(2 * $i18), b20);
vst1q_u64($nextp.add(2 * $i19), b21);
vst1q_u64($nextp.add(2 * $i20), c20);
vst1q_u64($nextp.add(2 * $i21), c21);
vst1q_u64($nextp.add(2 * $i22), d20);
vst1q_u64($nextp.add(2 * $i23), d21);
vst1q_u64($nextp.add(2 * $i24), a30);
vst1q_u64($nextp.add(2 * $i25), a31);
vst1q_u64($nextp.add(2 * $i26), b30);
vst1q_u64($nextp.add(2 * $i27), b31);
vst1q_u64($nextp.add(2 * $i28), c30);
vst1q_u64($nextp.add(2 * $i29), c31);
vst1q_u64($nextp.add(2 * $i30), d30);
vst1q_u64($nextp.add(2 * $i31), d31);
}};
}
type State = [uint64x2_t; OWORDS_IN_BLOCK];
#[inline(always)]
unsafe fn zero_state() -> State {
[unsafe { zero() }; OWORDS_IN_BLOCK]
}
#[inline(always)]
unsafe fn fill_block<const TBL: bool, const ML: u8, const X32: bool, const V: u8>(
state: &mut State,
prev_block: *const u64,
ref_block: *const u64,
next_block: *mut u64,
with_xor: bool,
) {
const {
assert!(
V == FUSED || V == FUSED2 || V == FUSED2_PREV || V == FUSED4,
"unknown fill_block shape"
)
};
unsafe {
let mut block_xy: [MaybeUninit<uint64x2_t>; OWORDS_IN_BLOCK] =
[const { MaybeUninit::uninit() }; OWORDS_IN_BLOCK];
if V == FUSED2 || V == FUSED2_PREV {
let carry = V != FUSED2_PREV;
let prevp = prev_block;
let refp = ref_block;
let nextp = next_block.cast_const();
col_group2!(
TBL, ML, X32, carry, prevp, state, block_xy, refp, nextp, with_xor, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
);
col_group2!(
TBL, ML, X32, carry, prevp, state, block_xy, refp, nextp, with_xor, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
);
col_group2!(
TBL, ML, X32, carry, prevp, state, block_xy, refp, nextp, with_xor, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47
);
col_group2!(
TBL, ML, X32, carry, prevp, state, block_xy, refp, nextp, with_xor, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63
);
row_group2!(
TBL, ML, X32, carry, state, block_xy, next_block, 0, 8, 16, 24, 32, 40, 48, 56, 1,
9, 17, 25, 33, 41, 49, 57
);
row_group2!(
TBL, ML, X32, carry, state, block_xy, next_block, 2, 10, 18, 26, 34, 42, 50, 58, 3,
11, 19, 27, 35, 43, 51, 59
);
row_group2!(
TBL, ML, X32, carry, state, block_xy, next_block, 4, 12, 20, 28, 36, 44, 52, 60, 5,
13, 21, 29, 37, 45, 53, 61
);
row_group2!(
TBL, ML, X32, carry, state, block_xy, next_block, 6, 14, 22, 30, 38, 46, 54, 62, 7,
15, 23, 31, 39, 47, 55, 63
);
} else if V == FUSED4 {
let refp = ref_block;
let nextp = next_block.cast_const();
col_group4!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31
);
col_group4!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63
);
row_group4!(
TBL, ML, X32, state, block_xy, next_block, 0, 8, 16, 24, 32, 40, 48, 56, 1, 9, 17,
25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42, 50, 58, 3, 11, 19, 27, 35, 43, 51, 59
);
row_group4!(
TBL, ML, X32, state, block_xy, next_block, 4, 12, 20, 28, 36, 44, 52, 60, 5, 13,
21, 29, 37, 45, 53, 61, 6, 14, 22, 30, 38, 46, 54, 62, 7, 15, 23, 31, 39, 47, 55,
63
);
} else if V == FUSED {
let refp = ref_block;
let nextp = next_block.cast_const();
col_group!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 0, 1, 2, 3, 4, 5, 6, 7
);
col_group!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 8, 9, 10, 11, 12, 13, 14, 15
);
col_group!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 16, 17, 18, 19, 20, 21, 22,
23
);
col_group!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 24, 25, 26, 27, 28, 29, 30,
31
);
col_group!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 32, 33, 34, 35, 36, 37, 38,
39
);
col_group!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 40, 41, 42, 43, 44, 45, 46,
47
);
col_group!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 48, 49, 50, 51, 52, 53, 54,
55
);
col_group!(
TBL, ML, X32, state, block_xy, refp, nextp, with_xor, 56, 57, 58, 59, 60, 61, 62,
63
);
row_group!(
TBL, ML, X32, state, block_xy, next_block, 0, 8, 16, 24, 32, 40, 48, 56
);
row_group!(
TBL, ML, X32, state, block_xy, next_block, 1, 9, 17, 25, 33, 41, 49, 57
);
row_group!(
TBL, ML, X32, state, block_xy, next_block, 2, 10, 18, 26, 34, 42, 50, 58
);
row_group!(
TBL, ML, X32, state, block_xy, next_block, 3, 11, 19, 27, 35, 43, 51, 59
);
row_group!(
TBL, ML, X32, state, block_xy, next_block, 4, 12, 20, 28, 36, 44, 52, 60
);
row_group!(
TBL, ML, X32, state, block_xy, next_block, 5, 13, 21, 29, 37, 45, 53, 61
);
row_group!(
TBL, ML, X32, state, block_xy, next_block, 6, 14, 22, 30, 38, 46, 54, 62
);
row_group!(
TBL, ML, X32, state, block_xy, next_block, 7, 15, 23, 31, 39, 47, 55, 63
);
}
}
}
#[inline(always)]
unsafe fn next_addresses<const TBL: bool, const ML: u8, const X32: bool, const V: u8>(
address_block: &mut Block,
input_block: &mut Block,
) {
unsafe {
let mut zero_block: State = zero_state();
let mut zero2_block: State = zero_state();
let zero_mem = Block::ZERO;
let zerop = zero_mem.as_ptr();
input_block.0[6] = input_block.0[6].wrapping_add(1);
let address = address_block.as_mut_ptr();
fill_block::<TBL, ML, X32, V>(&mut zero_block, zerop, input_block.as_ptr(), address, false);
fill_block::<TBL, ML, X32, V>(
&mut zero2_block,
zerop,
address.cast_const(),
address,
false,
);
}
}
#[inline(always)]
unsafe fn fill_segment_impl<const TBL: bool, const ML: u8, const X32: bool, const V: u8>(
instance: &Instance,
mut position: Position,
) {
if instance.lane_length == 0 || instance.lanes == 0 {
return;
}
let data_independent_addressing = instance.data_independent_addressing(&position);
let with_xor = instance.with_xor(position.pass);
let mut address_block = Block::ZERO;
let mut input_block = if data_independent_addressing {
instance.address_input_block(&position)
} else {
Block::ZERO
};
unsafe {
let mut state: State = zero_state();
let mut starting_index: u32 = 0;
if position.pass == 0 && position.slice == 0 {
starting_index = 2;
if data_independent_addressing {
next_addresses::<TBL, ML, X32, V>(&mut address_block, &mut input_block);
}
}
let mut curr_offset = position
.lane
.wrapping_mul(instance.lane_length)
.wrapping_add(position.slice.wrapping_mul(instance.segment_length))
.wrapping_add(starting_index);
#[allow(clippy::manual_is_multiple_of)]
let mut prev_offset = if curr_offset % instance.lane_length == 0 {
curr_offset
.wrapping_add(instance.lane_length)
.wrapping_sub(1)
} else {
curr_offset.wrapping_sub(1)
};
if V != FUSED2_PREV {
let prev_ptr = instance.block_ptr(prev_offset).cast::<u64>();
for (i, slot) in state.iter_mut().enumerate() {
*slot = vld1q_u64(prev_ptr.add(2 * i));
}
}
let mut i = starting_index;
while i < instance.segment_length {
if curr_offset % instance.lane_length == 1 {
prev_offset = curr_offset.wrapping_sub(1);
}
let pseudo_rand: u64 = if data_independent_addressing {
let slot = (i % ADDRESSES_IN_BLOCK_U32) as usize;
if slot == 0 {
next_addresses::<TBL, ML, X32, V>(&mut address_block, &mut input_block);
}
address_block.0[slot]
} else {
instance.block_ptr(prev_offset).cast::<u64>().read()
};
let mut ref_lane = ((pseudo_rand >> 32) % u64::from(instance.lanes)) as u32;
if position.pass == 0 && position.slice == 0 {
ref_lane = position.lane;
}
position.index = i;
let ref_index = crate::core::index_alpha(
instance,
&position,
(pseudo_rand & 0xFFFF_FFFF) as u32,
ref_lane == position.lane,
);
let ref_offset_u64 =
u64::from(instance.lane_length) * u64::from(ref_lane) + u64::from(ref_index);
debug_assert!(ref_offset_u64 < instance.memory_len() as u64);
let ref_offset = ref_offset_u64 as u32;
let ref_ptr = instance.block_ptr(ref_offset).cast::<u64>().cast_const();
let curr_ptr = instance.block_ptr(curr_offset).cast::<u64>();
let prev_ptr = instance.block_ptr(prev_offset).cast::<u64>().cast_const();
fill_block::<TBL, ML, X32, V>(&mut state, prev_ptr, ref_ptr, curr_ptr, with_xor);
i += 1;
curr_offset = curr_offset.wrapping_add(1);
prev_offset = prev_offset.wrapping_add(1);
}
}
}
#[target_feature(enable = "neon")]
pub unsafe fn fill_segment(instance: &Instance, position: Position) {
unsafe {
fill_segment_impl::<SHIFT_ROTATES, MUL_DEFAULT, ROR32_DEFAULT, SHAPE_DEFAULT>(
instance, position,
)
}
}
#[cfg(any(test, feature = "internal-api"))]
#[doc(hidden)]
#[target_feature(enable = "neon")]
pub unsafe fn fill_segment_tbl_rotates(instance: &Instance, position: Position) {
unsafe {
fill_segment_impl::<TABLE_ROTATES, MUL_DEFAULT, ROR32_DEFAULT, SHAPE_DEFAULT>(
instance, position,
)
}
}
#[cfg(any(test, feature = "internal-api"))]
#[doc(hidden)]
#[target_feature(enable = "neon")]
pub unsafe fn fill_segment_variant<const TBL: bool, const ML: u8, const X32: bool, const V: u8>(
instance: &Instance,
position: Position,
) {
unsafe { fill_segment_impl::<TBL, ML, X32, V>(instance, position) }
}
#[cfg(any(test, feature = "internal-api"))]
#[doc(hidden)]
#[target_feature(enable = "neon")]
pub unsafe fn fill_block_isolated(
prev_block: &Block,
ref_block: &Block,
next_block: &mut Block,
with_xor: bool,
) {
unsafe {
let mut state: State = zero_state();
let prev = prev_block.as_ptr();
for (i, slot) in state.iter_mut().enumerate() {
*slot = vld1q_u64(prev.add(2 * i));
}
fill_block::<SHIFT_ROTATES, MUL_DEFAULT, ROR32_DEFAULT, SHAPE_DEFAULT>(
&mut state,
prev,
ref_block.as_ptr(),
next_block.as_mut_ptr(),
with_xor,
);
}
}
#[cfg(any(test, feature = "internal-api"))]
#[doc(hidden)]
#[target_feature(enable = "neon")]
pub unsafe fn fill_block_isolated_tbl_rotates(
prev_block: &Block,
ref_block: &Block,
next_block: &mut Block,
with_xor: bool,
) {
unsafe {
let mut state: State = zero_state();
let prev = prev_block.as_ptr();
for (i, slot) in state.iter_mut().enumerate() {
*slot = vld1q_u64(prev.add(2 * i));
}
fill_block::<TABLE_ROTATES, MUL_DEFAULT, ROR32_DEFAULT, SHAPE_DEFAULT>(
&mut state,
prev,
ref_block.as_ptr(),
next_block.as_mut_ptr(),
with_xor,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
const NEON_BEATS_SCALAR_PREMISE: bool =
cfg!(all(target_vendor = "apple", target_arch = "aarch64"));
use crate::core::hash_traced;
use crate::fill_block::{Backend, detect, scalar};
use crate::params::{Algorithm, Params, QWORDS_IN_BLOCK, Version};
fn sm(x: u64) -> u64 {
let x = x.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = x;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
unsafe fn lanes(v: uint64x2_t) -> [u64; 2] {
let mut out = [0u64; 2];
unsafe { vst1q_u64(out.as_mut_ptr(), v) };
out
}
unsafe fn pack(a: u64, b: u64) -> uint64x2_t {
let src = [a, b];
unsafe { vld1q_u64(src.as_ptr()) }
}
#[test]
fn zero_is_all_zero() {
let z = unsafe { lanes(zero()) };
assert_eq!(z, [0, 0]);
}
#[test]
fn f_blamka_matches_the_scalar_definition() {
for k in 0..2048u64 {
let (x0, x1) = (sm(k), sm(k ^ 0x5555));
let (y0, y1) = (sm(k ^ 0xDEAD_BEEF), sm(k ^ 0xF00D));
let umull = unsafe { lanes(f_blamka::<MUL_UMULL>(pack(x0, x1), pack(y0, y1))) };
let umlal = unsafe { lanes(f_blamka::<MUL_UMLAL>(pack(x0, x1), pack(y0, y1))) };
let uzp = unsafe { lanes(f_blamka::<MUL_UZP>(pack(x0, x1), pack(y0, y1))) };
for (spelling, got) in [("UMULL", umull), ("UMLAL", umlal), ("UZP", uzp)] {
assert_eq!(got[0], scalar::f_blamka(x0, y0), "{spelling} lane 0, k={k}");
assert_eq!(got[1], scalar::f_blamka(x1, y1), "{spelling} lane 1, k={k}");
}
}
}
#[test]
fn rotations_match_u64_rotate_right() {
let mut cases: alloc::vec::Vec<u64> = alloc::vec![
0,
1,
u64::MAX,
1 << 63,
0x0123_4567_89AB_CDEF,
0xFEDC_BA98_7654_3210,
0xFFFF_FFFF_0000_0000,
0x0000_0000_FFFF_FFFF,
];
for k in 0..512u64 {
cases.push(sm(k));
}
for w in cases.chunks(2) {
let (a, b) = (w[0], *w.get(1).unwrap_or(&0));
unsafe {
let v = pack(a, b);
for got in [lanes(ror32::<ROR32_REV>(v)), lanes(ror32::<ROR32_XAR>(v))] {
assert_eq!(
got,
[a.rotate_right(32), b.rotate_right(32)],
"ror32 {a:#x}"
);
}
assert_eq!(lanes(ror63(v)), [a.rotate_right(63), b.rotate_right(63)]);
for got in [
lanes(ror24::<SHIFT_ROTATES>(v)),
lanes(ror24::<TABLE_ROTATES>(v)),
] {
assert_eq!(
got,
[a.rotate_right(24), b.rotate_right(24)],
"ror24 {a:#x}"
);
}
for got in [
lanes(ror16::<SHIFT_ROTATES>(v)),
lanes(ror16::<TABLE_ROTATES>(v)),
] {
assert_eq!(
got,
[a.rotate_right(16), b.rotate_right(16)],
"ror16 {a:#x}"
);
}
}
}
}
#[test]
fn alignr8_takes_the_high_lane_of_lo_then_the_low_lane_of_hi() {
let got = unsafe { lanes(alignr8(pack(10, 11), pack(20, 21))) };
assert_eq!(got, [21, 10]);
}
#[test]
fn diagonalize_produces_the_blake2_diagonals() {
unsafe {
let a0 = pack(0, 1);
let a1 = pack(2, 3);
let mut b0 = pack(4, 5);
let mut b1 = pack(6, 7);
let mut c0 = pack(8, 9);
let mut c1 = pack(10, 11);
let mut d0 = pack(12, 13);
let mut d1 = pack(14, 15);
diagonalize!(a0, b0, c0, d0, a1, b1, c1, d1);
let col = |a: uint64x2_t, b: uint64x2_t, c: uint64x2_t, d: uint64x2_t, l: usize| {
[lanes(a)[l], lanes(b)[l], lanes(c)[l], lanes(d)[l]]
};
assert_eq!(col(a0, b0, c0, d0, 0), [0, 5, 10, 15]);
assert_eq!(col(a0, b0, c0, d0, 1), [1, 6, 11, 12]);
assert_eq!(col(a1, b1, c1, d1, 0), [2, 7, 8, 13]);
assert_eq!(col(a1, b1, c1, d1, 1), [3, 4, 9, 14]);
undiagonalize!(a0, b0, c0, d0, a1, b1, c1, d1);
assert_eq!(lanes(a0), [0, 1]);
assert_eq!(lanes(a1), [2, 3]);
assert_eq!(lanes(b0), [4, 5]);
assert_eq!(lanes(b1), [6, 7]);
assert_eq!(lanes(c0), [8, 9]);
assert_eq!(lanes(c1), [10, 11]);
assert_eq!(lanes(d0), [12, 13]);
assert_eq!(lanes(d1), [14, 15]);
}
}
#[test]
fn fill_block_matches_scalar_over_random_triples() {
const CASES: u64 = 2048;
let mut counter = 0u64;
let mut next = move || {
counter = counter.wrapping_add(1);
sm(counter)
};
for case in 0..CASES {
let mut prev = Block::ZERO;
let mut reference = Block::ZERO;
let mut original = Block::ZERO;
for i in 0..QWORDS_IN_BLOCK {
prev.0[i] = next();
reference.0[i] = next();
original.0[i] = next();
}
for with_xor in [false, true] {
let mut want = original;
scalar::fill_block(&prev, &reference, &mut want, with_xor);
let mut got = original;
unsafe { fill_block_isolated(&prev, &reference, &mut got, with_xor) };
let mut got_tbl = original;
unsafe {
fill_block_isolated_tbl_rotates(&prev, &reference, &mut got_tbl, with_xor)
};
for i in 0..QWORDS_IN_BLOCK {
assert_eq!(
got.0[i], want.0[i],
"case {case} with_xor={with_xor}: word {i}"
);
assert_eq!(
got_tbl.0[i], want.0[i],
"case {case} with_xor={with_xor} (TBL): word {i}"
);
}
}
}
}
#[test]
fn fill_block_handles_the_degenerate_inputs() {
for fill in [0x00u8, 0xFFu8] {
let mut prev = Block::ZERO;
prev.fill(fill);
let mut reference = Block::ZERO;
reference.fill(fill ^ 0xFF);
for with_xor in [false, true] {
let mut want = Block::ZERO;
want.fill(0x5A);
let mut got = want;
scalar::fill_block(&prev, &reference, &mut want, with_xor);
unsafe { fill_block_isolated(&prev, &reference, &mut got, with_xor) };
assert_eq!(got, want, "fill={fill:#04x} with_xor={with_xor}");
}
}
let mut zero_out = Block::ZERO;
unsafe { fill_block_isolated(&Block::ZERO, &Block::ZERO, &mut zero_out, false) };
assert_eq!(zero_out, Block::ZERO);
}
#[test]
fn next_addresses_matches_scalar() {
let mut want_addr = Block::ZERO;
let mut want_in = Block::ZERO;
let mut got_addr = Block::ZERO;
let mut got_in = Block::ZERO;
for b in [&mut want_in, &mut got_in] {
b.0[0] = 1;
b.0[1] = 2;
b.0[2] = 3;
b.0[3] = 4096;
b.0[4] = 3;
b.0[5] = 2;
}
for round in 0..4 {
scalar::next_addresses(&mut want_addr, &mut want_in);
unsafe {
next_addresses::<SHIFT_ROTATES, MUL_DEFAULT, ROR32_DEFAULT, SHAPE_DEFAULT>(
&mut got_addr,
&mut got_in,
)
};
assert_eq!(got_in.0[6], want_in.0[6], "counter after round {round}");
assert_eq!(got_addr, want_addr, "address block after round {round}");
}
assert_eq!(want_in.0[6], 4);
}
#[test]
fn detection_selects_neon_on_this_host() {
const { assert!(cfg!(target_arch = "aarch64")) };
assert!(Backend::Neon.is_available(), "NEON must be available");
if NEON_BEATS_SCALAR_PREMISE {
assert_eq!(detect(), Backend::Neon, "aarch64 must resolve to Neon");
assert_eq!(crate::fill_block::backend(), Backend::Neon);
} else {
assert!(
matches!(detect(), Backend::Neon | Backend::Scalar),
"detection must pick one of the two runnable backends"
);
}
let available: alloc::vec::Vec<Backend> = Backend::ALL
.iter()
.copied()
.filter(|b| b.is_available())
.collect();
assert_eq!(available, alloc::vec![Backend::Scalar, Backend::Neon]);
if !cfg!(miri) {
assert!(core::ptr::fn_addr_eq(
crate::fill_block::fill_segment_fn(Backend::Neon),
fill_segment as crate::fill_block::FillSegmentFn,
));
assert!(!core::ptr::fn_addr_eq(
crate::fill_block::fill_segment_fn(Backend::Neon),
crate::fill_block::fill_segment_fn(Backend::Scalar),
));
}
}
fn hash(
backend: Backend,
alg: Algorithm,
ver: Version,
params: &Params,
salt: &[u8],
) -> [u8; 32] {
assert!(
backend.is_available(),
"{backend} cannot run on this CPU; calling it would be UB"
);
let mut out = [0u8; 32];
unsafe {
hash_traced(
backend,
alg,
ver,
params,
b"password",
salt,
&[],
&[],
&mut out,
None,
)
}
.expect("hash");
out
}
#[test]
fn whole_hash_matches_scalar_across_the_parameter_matrix() {
for alg in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
for ver in [Version::V0x10, Version::V0x13] {
for lanes in [1u32, 2, 3, 4] {
for t_cost in [1u32, 2, 3] {
for m_cost in [8 * lanes, 4 * 128 * lanes, 4 * 200 * lanes + 3] {
let params = Params::new(m_cost, t_cost, lanes, 32).expect("params");
let want = hash(Backend::Scalar, alg, ver, ¶ms, b"somesalt");
let got = hash(Backend::Neon, alg, ver, ¶ms, b"somesalt");
assert_eq!(
got, want,
"{alg:?} {ver:?} lanes={lanes} t={t_cost} m={m_cost}"
);
}
}
}
}
}
}
#[test]
fn every_tuning_variant_agrees_at_segment_level() {
let params = Params::new(4 * 32, 1, 1, 32).expect("params");
let (memory_blocks, _, _) = params.memory_layout();
let n = memory_blocks as usize;
let positions = [
Position::new(0, 0, 0, 0),
Position::new(0, 0, 1, 0),
Position::new(0, 0, 2, 0),
Position::new(0, 0, 3, 0),
Position::new(1, 0, 0, 0),
Position::new(1, 0, 1, 0),
];
for alg in [Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id] {
for position in positions {
let mut start: alloc::vec::Vec<Block> = alloc::vec![Block::ZERO; n];
for (bi, block) in start.iter_mut().enumerate() {
for wi in 0..QWORDS_IN_BLOCK {
block.0[wi] = sm((bi * QWORDS_IN_BLOCK + wi) as u64);
}
}
let mut want = start.clone();
let want_ptr = want.as_mut_ptr();
unsafe {
let a = Instance::new(want_ptr, n, alg, Version::V0x13, ¶ms);
fill_segment(&a, position);
}
type V = unsafe fn(&Instance, Position);
let variants: [(&str, V); 18] = [
("tbl rotates", fill_segment_tbl_rotates),
(
"x1/UMULL/REV",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_REV, FUSED>,
),
(
"x1/UMLAL/XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMLAL, ROR32_XAR, FUSED>,
),
(
"x1/UZP/XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP, ROR32_XAR, FUSED>,
),
(
"x2/UMULL/REV",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_REV, FUSED2>,
),
(
"x2/UMULL/XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_XAR, FUSED2>,
),
(
"x2/UMLAL/REV",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMLAL, ROR32_REV, FUSED2>,
),
(
"x2/UZP/XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP, ROR32_XAR, FUSED2>,
),
(
"x2/TBL/UMLAL/XAR",
fill_segment_variant::<TABLE_ROTATES, MUL_UMLAL, ROR32_XAR, FUSED2>,
),
(
"x1/UZPPAIR/XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED>,
),
(
"x2/UZPPAIR/REV",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_REV, FUSED2>,
),
(
"x2/TBL/UZPPAIR/XAR",
fill_segment_variant::<TABLE_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2>,
),
(
"x2/UZPPAIR+UMLAL/XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR_MLAL, ROR32_XAR, FUSED2>,
),
(
"x2/UZPPAIR/XAR/prev",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2_PREV>,
),
(
"x2/UMULL/XAR/prev",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_XAR, FUSED2_PREV>,
),
(
"x4/UZPPAIR/XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED4>,
),
(
"x4/UMULL/XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_XAR, FUSED4>,
),
(
"x4/TBL/UZPPAIR/REV",
fill_segment_variant::<TABLE_ROTATES, MUL_UZP_PAIR, ROR32_REV, FUSED4>,
),
];
for (name, f) in variants {
let mut got = start.clone();
let got_ptr = got.as_mut_ptr();
unsafe {
let b = Instance::new(got_ptr, n, alg, Version::V0x13, ¶ms);
f(&b, position);
}
assert_eq!(got, want, "{name}: {alg:?} {position:?}");
}
}
}
}
#[test]
fn threads_do_not_change_the_neon_tag() {
for lanes in [2u32, 4] {
for threads in 1..=lanes {
let single =
Params::new_with_threads(4 * 40 * lanes, 2, lanes, 1, 32).expect("params");
let multi = Params::new_with_threads(4 * 40 * lanes, 2, lanes, threads, 32)
.expect("params");
assert_eq!(
hash(
Backend::Neon,
Algorithm::Argon2id,
Version::V0x13,
&single,
b"somesalt"
),
hash(
Backend::Neon,
Algorithm::Argon2id,
Version::V0x13,
&multi,
b"somesalt"
),
"lanes={lanes} threads={threads}"
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn check_vector(
alg: Algorithm,
ver: Version,
t_cost: u32,
m_log2: u32,
lanes: u32,
pwd: &[u8],
salt: &[u8],
want_hex: &str,
) {
let params = Params::new(1 << m_log2, t_cost, lanes, 32).expect("params");
assert!(
Backend::Neon.is_available(),
"NEON is baseline on aarch64; forcing it without it would be UB"
);
let mut out = [0u8; 32];
unsafe {
hash_traced(
Backend::Neon,
alg,
ver,
¶ms,
pwd,
salt,
&[],
&[],
&mut out,
None,
)
}
.expect("hash");
let mut hex = alloc::string::String::new();
for byte in out {
use core::fmt::Write as _;
write!(hex, "{byte:02x}").expect("write");
}
assert_eq!(hex, want_hex, "{alg:?} {ver:?} t={t_cost} m=2^{m_log2}");
let mut scalar_out = [0u8; 32];
let scalar = unsafe {
hash_traced(
Backend::Scalar,
alg,
ver,
¶ms,
pwd,
salt,
&[],
&[],
&mut scalar_out,
None,
)
};
scalar.expect("hash");
assert_eq!(out, scalar_out, "neon vs scalar");
}
macro_rules! official {
($name:ident, $line:expr,
$alg:expr, $ver:expr, t=$t:expr, m_log2=$m:expr, lanes=$p:expr,
$pwd:expr, $salt:expr, $hex:expr,) => {
#[test]
fn $name() {
let _ = $line; check_vector($alg, $ver, $t, $m, $p, $pwd, $salt, $hex);
}
};
}
macro_rules! official_large {
($name:ident, $line:expr,
$alg:expr, $ver:expr, t=$t:expr, m_log2=$m:expr, lanes=$p:expr,
$pwd:expr, $salt:expr, $hex:expr,) => {
#[test]
#[ignore = "TEST_LARGE_RAM in test.c: needs 1 GiB"]
fn $name() {
let _ = $line;
check_vector($alg, $ver, $t, $m, $p, $pwd, $salt, $hex);
}
};
}
official! {
argon2i_v0x10_t2_m16_p1_password_somesalt, 77,
Algorithm::Argon2i, Version::V0x10, t=2, m_log2=16, lanes=1,
b"password", b"somesalt",
"f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694",
}
official_large! {
argon2i_v0x10_t2_m20_p1_password_somesalt, 82,
Algorithm::Argon2i, Version::V0x10, t=2, m_log2=20, lanes=1,
b"password", b"somesalt",
"9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9",
}
official! {
argon2i_v0x10_t2_m18_p1_password_somesalt, 87,
Algorithm::Argon2i, Version::V0x10, t=2, m_log2=18, lanes=1,
b"password", b"somesalt",
"3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467",
}
official! {
argon2i_v0x10_t2_m8_p1_password_somesalt, 91,
Algorithm::Argon2i, Version::V0x10, t=2, m_log2=8, lanes=1,
b"password", b"somesalt",
"fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06",
}
official! {
argon2i_v0x10_t2_m8_p2_password_somesalt, 95,
Algorithm::Argon2i, Version::V0x10, t=2, m_log2=8, lanes=2,
b"password", b"somesalt",
"b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb",
}
official! {
argon2i_v0x10_t1_m16_p1_password_somesalt, 99,
Algorithm::Argon2i, Version::V0x10, t=1, m_log2=16, lanes=1,
b"password", b"somesalt",
"81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2",
}
official! {
argon2i_v0x10_t4_m16_p1_password_somesalt, 103,
Algorithm::Argon2i, Version::V0x10, t=4, m_log2=16, lanes=1,
b"password", b"somesalt",
"f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b",
}
official! {
argon2i_v0x10_t2_m16_p1_differentpassword_somesalt, 107,
Algorithm::Argon2i, Version::V0x10, t=2, m_log2=16, lanes=1,
b"differentpassword", b"somesalt",
"e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3",
}
official! {
argon2i_v0x10_t2_m16_p1_password_diffsalt, 111,
Algorithm::Argon2i, Version::V0x10, t=2, m_log2=16, lanes=1,
b"password", b"diffsalt",
"79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497",
}
official! {
argon2i_v0x13_t2_m16_p1_password_somesalt, 156,
Algorithm::Argon2i, Version::V0x13, t=2, m_log2=16, lanes=1,
b"password", b"somesalt",
"c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
}
official_large! {
argon2i_v0x13_t2_m20_p1_password_somesalt, 161,
Algorithm::Argon2i, Version::V0x13, t=2, m_log2=20, lanes=1,
b"password", b"somesalt",
"d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41",
}
official! {
argon2i_v0x13_t2_m18_p1_password_somesalt, 166,
Algorithm::Argon2i, Version::V0x13, t=2, m_log2=18, lanes=1,
b"password", b"somesalt",
"296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb",
}
official! {
argon2i_v0x13_t2_m8_p1_password_somesalt, 170,
Algorithm::Argon2i, Version::V0x13, t=2, m_log2=8, lanes=1,
b"password", b"somesalt",
"89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f",
}
official! {
argon2i_v0x13_t2_m8_p2_password_somesalt, 174,
Algorithm::Argon2i, Version::V0x13, t=2, m_log2=8, lanes=2,
b"password", b"somesalt",
"4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61",
}
official! {
argon2i_v0x13_t1_m16_p1_password_somesalt, 178,
Algorithm::Argon2i, Version::V0x13, t=1, m_log2=16, lanes=1,
b"password", b"somesalt",
"d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf",
}
official! {
argon2i_v0x13_t4_m16_p1_password_somesalt, 182,
Algorithm::Argon2i, Version::V0x13, t=4, m_log2=16, lanes=1,
b"password", b"somesalt",
"aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b",
}
official! {
argon2i_v0x13_t2_m16_p1_differentpassword_somesalt, 186,
Algorithm::Argon2i, Version::V0x13, t=2, m_log2=16, lanes=1,
b"differentpassword", b"somesalt",
"14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee",
}
official! {
argon2i_v0x13_t2_m16_p1_password_diffsalt, 190,
Algorithm::Argon2i, Version::V0x13, t=2, m_log2=16, lanes=1,
b"password", b"diffsalt",
"b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271",
}
official! {
argon2id_v0x13_t2_m16_p1_password_somesalt, 233,
Algorithm::Argon2id, Version::V0x13, t=2, m_log2=16, lanes=1,
b"password", b"somesalt",
"09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7",
}
official! {
argon2id_v0x13_t2_m18_p1_password_somesalt, 237,
Algorithm::Argon2id, Version::V0x13, t=2, m_log2=18, lanes=1,
b"password", b"somesalt",
"78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c",
}
official! {
argon2id_v0x13_t2_m8_p1_password_somesalt, 241,
Algorithm::Argon2id, Version::V0x13, t=2, m_log2=8, lanes=1,
b"password", b"somesalt",
"9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe",
}
official! {
argon2id_v0x13_t2_m8_p2_password_somesalt, 245,
Algorithm::Argon2id, Version::V0x13, t=2, m_log2=8, lanes=2,
b"password", b"somesalt",
"6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037",
}
official! {
argon2id_v0x13_t1_m16_p1_password_somesalt, 249,
Algorithm::Argon2id, Version::V0x13, t=1, m_log2=16, lanes=1,
b"password", b"somesalt",
"f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98",
}
official! {
argon2id_v0x13_t4_m16_p1_password_somesalt, 253,
Algorithm::Argon2id, Version::V0x13, t=4, m_log2=16, lanes=1,
b"password", b"somesalt",
"9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c",
}
official! {
argon2id_v0x13_t2_m16_p1_differentpassword_somesalt, 257,
Algorithm::Argon2id, Version::V0x13, t=2, m_log2=16, lanes=1,
b"differentpassword", b"somesalt",
"0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde",
}
official! {
argon2id_v0x13_t2_m16_p1_password_diffsalt, 261,
Algorithm::Argon2id, Version::V0x13, t=2, m_log2=16, lanes=1,
b"password", b"diffsalt",
"bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c",
}
#[cfg(feature = "std")]
fn best_of(reps: usize, mut f: impl FnMut()) -> f64 {
let mut best = f64::MAX;
for _ in 0..reps {
let started = std::time::Instant::now();
f();
let elapsed = started.elapsed().as_secs_f64();
if elapsed < best {
best = elapsed;
}
}
best
}
#[cfg(feature = "std")]
fn best_of_interleaved(reps: usize, mut a: impl FnMut(), mut b: impl FnMut()) -> (f64, f64) {
let (mut best_a, mut best_b) = (f64::MAX, f64::MAX);
let sample = |f: &mut dyn FnMut(), best: &mut f64| {
let started = std::time::Instant::now();
f();
let elapsed = started.elapsed().as_secs_f64();
if elapsed < *best {
*best = elapsed;
}
};
for rep in 0..reps {
if rep % 2 == 0 {
sample(&mut a, &mut best_a);
sample(&mut b, &mut best_b);
} else {
sample(&mut b, &mut best_b);
sample(&mut a, &mut best_a);
}
}
(best_a, best_b)
}
#[cfg(feature = "std")]
#[test]
#[cfg_attr(debug_assertions, ignore = "meaningless without optimisations")]
fn compression_throughput_by_backend() {
const ITERS: usize = 200_000;
let mut prev = Block::ZERO;
let mut reference = Block::ZERO;
let mut next = Block::ZERO;
for i in 0..QWORDS_IN_BLOCK {
prev.0[i] = sm(i as u64);
reference.0[i] = sm(1000 + i as u64);
next.0[i] = sm(2000 + i as u64);
}
let report = |label: &str, seconds: f64| {
let ns = seconds * 1e9 / ITERS as f64;
std::eprintln!(
" {label:<22} {ns:>7.2} ns/block ({:>5.2} GiB/s)",
(ITERS as f64 * 1024.0) / seconds / (1024.0 * 1024.0 * 1024.0)
);
ns
};
const WARMUP: usize = ITERS / 10;
const REPS: usize = 3;
const CHAIN: usize = WARMUP + REPS * ITERS;
std::eprintln!("fill_block, L1-resident, best of {REPS} x {ITERS} calls:");
let (scalar_ns, scalar_out) = {
let mut n = next;
for _ in 0..WARMUP {
scalar::fill_block(&prev, &reference, &mut n, true);
}
let s = best_of(REPS, || {
for _ in 0..ITERS {
scalar::fill_block(&prev, &reference, &mut n, true);
}
});
(report("scalar", s), n)
};
let (neon_ns, neon_out) = {
let mut n = next;
unsafe {
for _ in 0..WARMUP {
fill_block_isolated(&prev, &reference, &mut n, true);
}
}
let s = best_of(REPS, || unsafe {
for _ in 0..ITERS {
fill_block_isolated(&prev, &reference, &mut n, true);
}
});
(report("neon (SHL+SRI)", s), n)
};
let (neon_tbl_ns, neon_tbl_out) = {
let mut n = next;
unsafe {
for _ in 0..WARMUP {
fill_block_isolated_tbl_rotates(&prev, &reference, &mut n, true);
}
}
let s = best_of(REPS, || unsafe {
for _ in 0..ITERS {
fill_block_isolated_tbl_rotates(&prev, &reference, &mut n, true);
}
});
(report("neon (TBL)", s), n)
};
std::eprintln!(
" -> neon/scalar x{:.2}, TBL/SHL+SRI x{:.3} (informational; \
regressions are CodSpeed's job)",
scalar_ns / neon_ns,
neon_tbl_ns / neon_ns
);
for i in 0..QWORDS_IN_BLOCK {
assert_eq!(
neon_out.0[i], scalar_out.0[i],
"NEON diverged from scalar at word {i} after a {CHAIN}-call chain"
);
assert_eq!(
neon_tbl_out.0[i], scalar_out.0[i],
"NEON (TBL) diverged from scalar at word {i} after a {CHAIN}-call chain"
);
}
}
#[cfg(feature = "std")]
fn seed_arena(arena: &mut [Block]) {
for (bi, block) in arena.iter_mut().enumerate() {
let base = (bi as u64) << 12;
for wi in 0..QWORDS_IN_BLOCK {
block.0[wi] = sm(base ^ wi as u64);
}
}
}
#[cfg(feature = "std")]
fn time_full_fill(
f: unsafe fn(&Instance, Position),
arena: &mut [Block],
params: &Params,
passes: u32,
) -> f64 {
let n = arena.len();
let ptr = arena.as_mut_ptr();
let instance =
unsafe { Instance::new(ptr, n, Algorithm::Argon2id, Version::V0x13, params) };
let started = std::time::Instant::now();
for pass in 0..passes {
for slice in 0..4u32 {
unsafe { f(&instance, Position::new(pass, 0, slice, 0)) };
}
}
started.elapsed().as_secs_f64()
}
#[cfg(feature = "std")]
fn paired_head_to_head(
a: (&str, unsafe fn(&Instance, Position)),
b: (&str, unsafe fn(&Instance, Position)),
) {
const REPS: usize = 31;
std::eprintln!(
"\n{:>8} {:>7} {:>20} {:>20} {:>8} {:>8}",
"m_cost",
"passes",
a.0,
b.0,
"x(min)",
"b wins"
);
for (m_cost, passes) in [
(1024u32, 2u32),
(4096, 2),
(16384, 2),
(65536, 1),
(65536, 2),
(262144, 2),
] {
let params = Params::new(m_cost, passes, 1, 32).expect("params");
let (memory_blocks, _, _) = params.memory_layout();
let n = memory_blocks as usize;
let blocks_filled = f64::from(memory_blocks) * f64::from(passes);
let mut arena: alloc::vec::Vec<Block> = alloc::vec![Block::ZERO; n];
let (mut sa, mut sb) = (
alloc::vec::Vec::with_capacity(REPS),
alloc::vec::Vec::with_capacity(REPS),
);
let mut b_wins = 0usize;
for rep in 0..REPS {
let (ta, tb) = if rep % 2 == 0 {
seed_arena(&mut arena);
let ta = time_full_fill(a.1, &mut arena, ¶ms, passes);
seed_arena(&mut arena);
let tb = time_full_fill(b.1, &mut arena, ¶ms, passes);
(ta, tb)
} else {
seed_arena(&mut arena);
let tb = time_full_fill(b.1, &mut arena, ¶ms, passes);
seed_arena(&mut arena);
let ta = time_full_fill(a.1, &mut arena, ¶ms, passes);
(ta, tb)
};
if tb < ta {
b_wins += 1;
}
sa.push(ta);
sb.push(tb);
}
let stat = |v: &mut alloc::vec::Vec<f64>| {
v.sort_by(f64::total_cmp);
(v[0], v[v.len() / 2])
};
let (amin, amed) = stat(&mut sa);
let (bmin, bmed) = stat(&mut sb);
std::eprintln!(
"{m_cost:>8} {passes:>7} {:>8.1} {:>8.1} ns {:>8.1} {:>8.1} ns {:>8.3} {b_wins:>4}/{REPS}",
amin * 1e9 / blocks_filled,
amed * 1e9 / blocks_filled,
bmin * 1e9 / blocks_filled,
bmed * 1e9 / blocks_filled,
amin / bmin,
);
}
std::eprintln!(
"(each pair of columns is min then median ns/block; \"b wins\" counts paired reps)"
);
}
#[cfg(feature = "std")]
#[test]
#[ignore = "scaffolding: run explicitly, it takes ~2 min"]
fn shipped_multiply_vs_the_spelling_it_replaced() {
paired_head_to_head(
(
"XTN x2 + UMULL",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_XAR, FUSED2>,
),
(
"UZP1 + UMULL/2",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2>,
),
);
}
#[cfg(feature = "std")]
#[test]
#[ignore = "scaffolding: run explicitly, it takes ~2 min"]
fn carried_state_vs_reread_prev() {
paired_head_to_head(
(
"carry state (opt.c)",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2>,
),
(
"re-read prev",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2_PREV>,
),
);
}
#[cfg(feature = "std")]
#[test]
#[ignore = "scaffolding: run explicitly, it takes ~2 min"]
fn two_way_vs_four_way_interleaving() {
paired_head_to_head(
(
"x2 (shipped)",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2>,
),
(
"x4",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED4>,
),
);
}
#[cfg(feature = "std")]
#[test]
#[ignore = "scaffolding: run explicitly, it takes ~1 min"]
fn fill_block_variant_shootout() {
const REPS: usize = 15;
type V = unsafe fn(&Instance, Position);
let candidates: [(&str, V); 15] = [
(
"x1, UMULL, REV64 (as ported)",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_REV, FUSED>,
),
(
"x2, UMULL, REV64",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_REV, FUSED2>,
),
(
"x2, UMULL, XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_XAR, FUSED2>,
),
(
"x2, UZP1, XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP, ROR32_XAR, FUSED2>,
),
(
"x2, UMLAL, XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMLAL, ROR32_XAR, FUSED2>,
),
(
"x2, UMLAL, XAR, TBL rot",
fill_segment_variant::<TABLE_ROTATES, MUL_UMLAL, ROR32_XAR, FUSED2>,
),
(
"x2, UZPPAIR, XAR (shipped)",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2>,
),
(
"x1, UZPPAIR, XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED>,
),
(
"x2, UZPPAIR, REV64",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_REV, FUSED2>,
),
(
"x2, UZPPAIR, XAR, TBL rot",
fill_segment_variant::<TABLE_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2>,
),
(
"x2, UZPPAIR+UMLAL, XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR_MLAL, ROR32_XAR, FUSED2>,
),
(
"x2, UZPPAIR, XAR, prev",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED2_PREV>,
),
(
"x2, UMULL, XAR, prev",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_XAR, FUSED2_PREV>,
),
(
"x4, UZPPAIR, XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UZP_PAIR, ROR32_XAR, FUSED4>,
),
(
"x4, UMULL, XAR",
fill_segment_variant::<SHIFT_ROTATES, MUL_UMULL, ROR32_XAR, FUSED4>,
),
];
for (label, m_cost) in [
("m=4096 (4 MiB, cache-resident)", 4096u32),
("m=65536 (64 MiB, DRAM-bound)", 1 << 16),
] {
let params = Params::new(m_cost, 2, 1, 32).expect("params");
let (memory_blocks, _, _) = params.memory_layout();
let n = memory_blocks as usize;
let blocks_filled = f64::from(memory_blocks) * 2.0;
let mut arena: alloc::vec::Vec<Block> = alloc::vec![Block::ZERO; n];
let mut samples: [alloc::vec::Vec<f64>; 15] =
core::array::from_fn(|_| alloc::vec::Vec::with_capacity(REPS));
for rep in 0..REPS {
for offset in 0..candidates.len() {
let k = (rep + offset) % candidates.len();
seed_arena(&mut arena);
samples[k].push(time_full_fill(candidates[k].1, &mut arena, ¶ms, 2));
}
}
let stat = |v: &[f64]| {
let mut v = v.to_vec();
v.sort_by(f64::total_cmp);
(v[0], v[v.len() / 2])
};
let (base_min, base_med) = stat(&samples[0]);
std::eprintln!("\n{label}, 2 passes, {REPS} reps, rotated round-robin:");
std::eprintln!(
" {:<30} {:>9} {:>9} {:>9} {:>9}",
"variant",
"min ms",
"med ms",
"ns/blk",
"x vs base"
);
for (k, (name, _)) in candidates.iter().enumerate() {
let (mn, med) = stat(&samples[k]);
std::eprintln!(
" {name:<30} {:>9.2} {:>9.2} {:>9.1} {:>8.3}x",
mn * 1e3,
med * 1e3,
mn * 1e9 / blocks_filled,
(base_min / mn + base_med / med) / 2.0,
);
}
}
}
#[cfg(feature = "std")]
#[test]
#[cfg_attr(debug_assertions, ignore = "meaningless without optimisations")]
fn neon_matches_scalar_on_large_arenas() {
for (label, m_cost) in [
("m=1024 (1 MiB, L2)", 1024u32),
("m=65536 (64 MiB)", 1 << 16),
] {
let params = Params::new(m_cost, 3, 1, 32).expect("params");
let blocks = f64::from(m_cost) * 3.0;
let once = |backend: Backend| {
hash(
backend,
Algorithm::Argon2id,
Version::V0x13,
¶ms,
b"somesalt",
)
};
let scalar_tag = once(Backend::Scalar);
let neon_tag = once(Backend::Neon);
assert_eq!(
neon_tag, scalar_tag,
"{label}: NEON tag differs from scalar"
);
let once = |backend: Backend| {
core::hint::black_box(once(backend));
};
let (scalar_s, neon_s) =
best_of_interleaved(5, || once(Backend::Scalar), || once(Backend::Neon));
std::eprintln!(
"argon2id t=3 p=1 {label}, best of 5 interleaved:\n \
scalar {:>8.2} ms ({:>6.1} ns/block, {:>5.2} GiB/s)\n \
neon {:>8.2} ms ({:>6.1} ns/block, {:>5.2} GiB/s)\n \
speedup x{:.2}",
scalar_s * 1e3,
scalar_s * 1e9 / blocks,
blocks * 1024.0 / scalar_s / (1024.0 * 1024.0 * 1024.0),
neon_s * 1e3,
neon_s * 1e9 / blocks,
blocks * 1024.0 / neon_s / (1024.0 * 1024.0 * 1024.0),
scalar_s / neon_s,
);
}
}
}