use core::arch::x86_64::*;
use core::mem::MaybeUninit;
use crate::block::{Block, Instance, Position};
use crate::params::{ADDRESSES_IN_BLOCK, BITS512_WORDS_IN_BLOCK};
const ADDRESSES_IN_BLOCK_U32: u32 = ADDRESSES_IN_BLOCK as u32;
const QUARTER_IDX: [i64; 8] = [0, 1, 4, 5, 2, 3, 6, 7];
#[inline(always)]
unsafe fn muladd(x: __m512i, y: __m512i) -> __m512i {
unsafe {
let z = _mm512_mul_epu32(x, y);
_mm512_add_epi64(_mm512_add_epi64(x, y), _mm512_add_epi64(z, z))
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn g1(
a0: &mut __m512i,
b0: &mut __m512i,
c0: &mut __m512i,
d0: &mut __m512i,
a1: &mut __m512i,
b1: &mut __m512i,
c1: &mut __m512i,
d1: &mut __m512i,
) {
unsafe {
*a0 = muladd(*a0, *b0);
*a1 = muladd(*a1, *b1);
*d0 = _mm512_xor_si512(*d0, *a0);
*d1 = _mm512_xor_si512(*d1, *a1);
*d0 = _mm512_ror_epi64::<32>(*d0);
*d1 = _mm512_ror_epi64::<32>(*d1);
*c0 = muladd(*c0, *d0);
*c1 = muladd(*c1, *d1);
*b0 = _mm512_xor_si512(*b0, *c0);
*b1 = _mm512_xor_si512(*b1, *c1);
*b0 = _mm512_ror_epi64::<24>(*b0);
*b1 = _mm512_ror_epi64::<24>(*b1);
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn g2(
a0: &mut __m512i,
b0: &mut __m512i,
c0: &mut __m512i,
d0: &mut __m512i,
a1: &mut __m512i,
b1: &mut __m512i,
c1: &mut __m512i,
d1: &mut __m512i,
) {
unsafe {
*a0 = muladd(*a0, *b0);
*a1 = muladd(*a1, *b1);
*d0 = _mm512_xor_si512(*d0, *a0);
*d1 = _mm512_xor_si512(*d1, *a1);
*d0 = _mm512_ror_epi64::<16>(*d0);
*d1 = _mm512_ror_epi64::<16>(*d1);
*c0 = muladd(*c0, *d0);
*c1 = muladd(*c1, *d1);
*b0 = _mm512_xor_si512(*b0, *c0);
*b1 = _mm512_xor_si512(*b1, *c1);
*b0 = _mm512_ror_epi64::<63>(*b0);
*b1 = _mm512_ror_epi64::<63>(*b1);
}
}
#[inline(always)]
unsafe fn diagonalize(
b0: &mut __m512i,
b1: &mut __m512i,
c0: &mut __m512i,
c1: &mut __m512i,
d0: &mut __m512i,
d1: &mut __m512i,
) {
unsafe {
*b0 = _mm512_permutex_epi64::<0b00_11_10_01>(*b0);
*b1 = _mm512_permutex_epi64::<0b00_11_10_01>(*b1);
*c0 = _mm512_permutex_epi64::<0b01_00_11_10>(*c0);
*c1 = _mm512_permutex_epi64::<0b01_00_11_10>(*c1);
*d0 = _mm512_permutex_epi64::<0b10_01_00_11>(*d0);
*d1 = _mm512_permutex_epi64::<0b10_01_00_11>(*d1);
}
}
#[inline(always)]
unsafe fn undiagonalize(
b0: &mut __m512i,
b1: &mut __m512i,
c0: &mut __m512i,
c1: &mut __m512i,
d0: &mut __m512i,
d1: &mut __m512i,
) {
unsafe {
*b0 = _mm512_permutex_epi64::<0b10_01_00_11>(*b0);
*b1 = _mm512_permutex_epi64::<0b10_01_00_11>(*b1);
*c0 = _mm512_permutex_epi64::<0b01_00_11_10>(*c0);
*c1 = _mm512_permutex_epi64::<0b01_00_11_10>(*c1);
*d0 = _mm512_permutex_epi64::<0b00_11_10_01>(*d0);
*d1 = _mm512_permutex_epi64::<0b00_11_10_01>(*d1);
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn blake2_round(
a0: __m512i,
b0: __m512i,
c0: __m512i,
d0: __m512i,
a1: __m512i,
b1: __m512i,
c1: __m512i,
d1: __m512i,
) -> [__m512i; 8] {
let (mut a0, mut b0, mut c0, mut d0) = (a0, b0, c0, d0);
let (mut a1, mut b1, mut c1, mut d1) = (a1, b1, c1, d1);
unsafe {
g1(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
g2(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
diagonalize(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
g1(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
g2(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
undiagonalize(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
}
[a0, b0, c0, d0, a1, b1, c1, d1]
}
#[inline(always)]
unsafe fn swap_halves(a0: &mut __m512i, a1: &mut __m512i) {
unsafe {
let t0 = _mm512_shuffle_i64x2::<0b01_00_01_00>(*a0, *a1);
let t1 = _mm512_shuffle_i64x2::<0b11_10_11_10>(*a0, *a1);
*a0 = t0;
*a1 = t1;
}
}
#[inline(always)]
unsafe fn swap_quarters(a0: &mut __m512i, a1: &mut __m512i) {
unsafe {
swap_halves(a0, a1);
let idx = _mm512_loadu_si512(QUARTER_IDX.as_ptr().cast());
*a0 = _mm512_permutexvar_epi64(idx, *a0);
*a1 = _mm512_permutexvar_epi64(idx, *a1);
}
}
#[inline(always)]
unsafe fn unswap_quarters(a0: &mut __m512i, a1: &mut __m512i) {
unsafe {
let idx = _mm512_loadu_si512(QUARTER_IDX.as_ptr().cast());
*a0 = _mm512_permutexvar_epi64(idx, *a0);
*a1 = _mm512_permutexvar_epi64(idx, *a1);
swap_halves(a0, a1);
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn blake2_round_1(
a0: __m512i,
c0: __m512i,
b0: __m512i,
d0: __m512i,
a1: __m512i,
c1: __m512i,
b1: __m512i,
d1: __m512i,
) -> [__m512i; 8] {
let (mut a0, mut c0, mut b0, mut d0) = (a0, c0, b0, d0);
let (mut a1, mut c1, mut b1, mut d1) = (a1, c1, b1, d1);
unsafe {
swap_halves(&mut a0, &mut b0);
swap_halves(&mut c0, &mut d0);
swap_halves(&mut a1, &mut b1);
swap_halves(&mut c1, &mut d1);
let r = blake2_round(a0, b0, c0, d0, a1, b1, c1, d1);
a0 = r[0];
b0 = r[1];
c0 = r[2];
d0 = r[3];
a1 = r[4];
b1 = r[5];
c1 = r[6];
d1 = r[7];
swap_halves(&mut a0, &mut b0);
swap_halves(&mut c0, &mut d0);
swap_halves(&mut a1, &mut b1);
swap_halves(&mut c1, &mut d1);
}
[a0, c0, b0, d0, a1, c1, b1, d1]
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn blake2_round_2(
a0: __m512i,
a1: __m512i,
b0: __m512i,
b1: __m512i,
c0: __m512i,
c1: __m512i,
d0: __m512i,
d1: __m512i,
) -> [__m512i; 8] {
let (mut a0, mut a1, mut b0, mut b1) = (a0, a1, b0, b1);
let (mut c0, mut c1, mut d0, mut d1) = (c0, c1, d0, d1);
unsafe {
swap_quarters(&mut a0, &mut a1);
swap_quarters(&mut b0, &mut b1);
swap_quarters(&mut c0, &mut c1);
swap_quarters(&mut d0, &mut d1);
let r = blake2_round(a0, b0, c0, d0, a1, b1, c1, d1);
a0 = r[0];
b0 = r[1];
c0 = r[2];
d0 = r[3];
a1 = r[4];
b1 = r[5];
c1 = r[6];
d1 = r[7];
unswap_quarters(&mut a0, &mut a1);
unswap_quarters(&mut b0, &mut b1);
unswap_quarters(&mut c0, &mut c1);
unswap_quarters(&mut d0, &mut d1);
}
[a0, a1, b0, b1, c0, c1, d0, d1]
}
#[inline(always)]
unsafe fn fill_block(
state: &mut [__m512i; BITS512_WORDS_IN_BLOCK],
ref_block: *const Block,
next_block: *mut Block,
with_xor: bool,
) {
let refp = ref_block.cast::<__m512i>();
let nextp = next_block.cast::<__m512i>();
let mut block_xy: [MaybeUninit<__m512i>; BITS512_WORDS_IN_BLOCK] =
[const { MaybeUninit::uninit() }; BITS512_WORDS_IN_BLOCK];
unsafe {
if with_xor {
for i in 0..BITS512_WORDS_IN_BLOCK {
state[i] = _mm512_xor_si512(state[i], _mm512_loadu_si512(refp.add(i)));
block_xy[i] = MaybeUninit::new(_mm512_xor_si512(
state[i],
_mm512_loadu_si512(nextp.add(i).cast_const()),
));
}
} else {
for i in 0..BITS512_WORDS_IN_BLOCK {
state[i] = _mm512_xor_si512(state[i], _mm512_loadu_si512(refp.add(i)));
block_xy[i] = MaybeUninit::new(state[i]);
}
}
}
macro_rules! round_1 {
($base:expr) => {{
let r = unsafe {
blake2_round_1(
state[$base],
state[$base + 1],
state[$base + 2],
state[$base + 3],
state[$base + 4],
state[$base + 5],
state[$base + 6],
state[$base + 7],
)
};
state[$base] = r[0];
state[$base + 1] = r[1];
state[$base + 2] = r[2];
state[$base + 3] = r[3];
state[$base + 4] = r[4];
state[$base + 5] = r[5];
state[$base + 6] = r[6];
state[$base + 7] = r[7];
}};
}
macro_rules! round_2 {
($i:expr) => {{
let r = unsafe {
blake2_round_2(
state[$i],
state[2 + $i],
state[4 + $i],
state[6 + $i],
state[8 + $i],
state[10 + $i],
state[12 + $i],
state[14 + $i],
)
};
state[$i] = r[0];
state[2 + $i] = r[1];
state[4 + $i] = r[2];
state[6 + $i] = r[3];
state[8 + $i] = r[4];
state[10 + $i] = r[5];
state[12 + $i] = r[6];
state[14 + $i] = r[7];
}};
}
round_1!(0);
round_1!(8);
round_2!(0);
round_2!(1);
unsafe {
for i in 0..BITS512_WORDS_IN_BLOCK {
state[i] = _mm512_xor_si512(state[i], block_xy[i].assume_init());
_mm512_storeu_si512(nextp.add(i), state[i]);
}
}
}
const PREFETCH_DISTANCE: u32 = 2;
#[inline(always)]
unsafe fn prefetch_block<const HINT: i32>(block: *const Block) {
let base = block.cast::<i8>();
unsafe {
let mut k = 0;
while k < core::mem::size_of::<Block>() {
_mm_prefetch::<HINT>(base.add(k));
k += 64;
}
}
}
#[inline(always)]
fn ref_offset_for(instance: &Instance, position: &Position, index: u32, pseudo_rand: u64) -> u32 {
let mut ref_lane = ((pseudo_rand >> 32) % u64::from(instance.lanes)) as u32;
if position.pass == 0 && position.slice == 0 {
ref_lane = position.lane;
}
let mut at = *position;
at.index = index;
let ref_index = crate::core::index_alpha(
instance,
&at,
(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);
ref_offset_u64 as u32
}
#[inline(always)]
unsafe fn next_addresses(address_block: *mut Block, input_block: *mut Block) {
let zero = unsafe { _mm512_setzero_si512() };
let mut zero_state = [zero; BITS512_WORDS_IN_BLOCK];
let mut zero2_state = [zero; BITS512_WORDS_IN_BLOCK];
unsafe {
(*input_block).0[6] = (*input_block).0[6].wrapping_add(1);
}
unsafe {
fill_block(
&mut zero_state,
input_block.cast_const(),
address_block,
false,
);
fill_block(
&mut zero2_state,
address_block.cast_const(),
address_block,
false,
);
}
}
#[inline(always)]
unsafe fn fill_segment_impl(instance: &Instance, 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
};
let mut starting_index: u32 = 0;
if position.pass == 0 && position.slice == 0 {
starting_index = 2;
if data_independent_addressing {
unsafe {
next_addresses(&raw mut address_block, &raw 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)
};
let mut state = unsafe {
let p = instance
.block_ptr(prev_offset)
.cast::<__m512i>()
.cast_const();
let mut state = [_mm512_setzero_si512(); BITS512_WORDS_IN_BLOCK];
for (i, slot) in state.iter_mut().enumerate() {
*slot = _mm512_loadu_si512(p.add(i));
}
state
};
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 slot = (i % ADDRESSES_IN_BLOCK_U32) as usize;
let pseudo_rand: u64 = if data_independent_addressing {
if slot == 0 {
unsafe {
next_addresses(&raw mut address_block, &raw mut input_block);
}
}
address_block.0[slot]
} else {
unsafe { (*instance.block_ptr(prev_offset)).0[0] }
};
let ref_offset = ref_offset_for(instance, &position, i, pseudo_rand);
if data_independent_addressing {
let ahead = i.wrapping_add(PREFETCH_DISTANCE);
let slot_ahead = (ahead % ADDRESSES_IN_BLOCK_U32) as usize;
if ahead < instance.segment_length && slot_ahead > slot {
let off = ref_offset_for(instance, &position, ahead, address_block.0[slot_ahead]);
unsafe { prefetch_block::<_MM_HINT_T0>(instance.block_ptr(off).cast_const()) };
}
}
unsafe {
fill_block(
&mut state,
instance.block_ptr(ref_offset).cast_const(),
instance.block_ptr(curr_offset),
with_xor,
);
}
i += 1;
curr_offset = curr_offset.wrapping_add(1);
prev_offset = prev_offset.wrapping_add(1);
}
}
#[target_feature(enable = "avx512f")]
pub unsafe fn fill_segment(instance: &Instance, position: Position) {
unsafe { fill_segment_impl(instance, position) }
}
#[cfg(test)]
fn simulated_fill_block(prev: &Block, ref_block: &Block, next_block: &mut Block, with_xor: bool) {
use tests::sim::{M512, add, mul_epu32, permutex, permutexvar, ror, shuffle_i64x2, xor};
fn muladd(x: M512, y: M512) -> M512 {
let z = mul_epu32(x, y);
add(add(x, y), add(z, z))
}
#[allow(clippy::too_many_arguments)]
fn g1(
a0: &mut M512,
b0: &mut M512,
c0: &mut M512,
d0: &mut M512,
a1: &mut M512,
b1: &mut M512,
c1: &mut M512,
d1: &mut M512,
) {
*a0 = muladd(*a0, *b0);
*a1 = muladd(*a1, *b1);
*d0 = xor(*d0, *a0);
*d1 = xor(*d1, *a1);
*d0 = ror::<32>(*d0);
*d1 = ror::<32>(*d1);
*c0 = muladd(*c0, *d0);
*c1 = muladd(*c1, *d1);
*b0 = xor(*b0, *c0);
*b1 = xor(*b1, *c1);
*b0 = ror::<24>(*b0);
*b1 = ror::<24>(*b1);
}
#[allow(clippy::too_many_arguments)]
fn g2(
a0: &mut M512,
b0: &mut M512,
c0: &mut M512,
d0: &mut M512,
a1: &mut M512,
b1: &mut M512,
c1: &mut M512,
d1: &mut M512,
) {
*a0 = muladd(*a0, *b0);
*a1 = muladd(*a1, *b1);
*d0 = xor(*d0, *a0);
*d1 = xor(*d1, *a1);
*d0 = ror::<16>(*d0);
*d1 = ror::<16>(*d1);
*c0 = muladd(*c0, *d0);
*c1 = muladd(*c1, *d1);
*b0 = xor(*b0, *c0);
*b1 = xor(*b1, *c1);
*b0 = ror::<63>(*b0);
*b1 = ror::<63>(*b1);
}
fn diagonalize(
b0: &mut M512,
b1: &mut M512,
c0: &mut M512,
c1: &mut M512,
d0: &mut M512,
d1: &mut M512,
) {
*b0 = permutex::<0b00_11_10_01>(*b0);
*b1 = permutex::<0b00_11_10_01>(*b1);
*c0 = permutex::<0b01_00_11_10>(*c0);
*c1 = permutex::<0b01_00_11_10>(*c1);
*d0 = permutex::<0b10_01_00_11>(*d0);
*d1 = permutex::<0b10_01_00_11>(*d1);
}
fn undiagonalize(
b0: &mut M512,
b1: &mut M512,
c0: &mut M512,
c1: &mut M512,
d0: &mut M512,
d1: &mut M512,
) {
*b0 = permutex::<0b10_01_00_11>(*b0);
*b1 = permutex::<0b10_01_00_11>(*b1);
*c0 = permutex::<0b01_00_11_10>(*c0);
*c1 = permutex::<0b01_00_11_10>(*c1);
*d0 = permutex::<0b00_11_10_01>(*d0);
*d1 = permutex::<0b00_11_10_01>(*d1);
}
#[allow(clippy::too_many_arguments)]
fn blake2_round(
a0: M512,
b0: M512,
c0: M512,
d0: M512,
a1: M512,
b1: M512,
c1: M512,
d1: M512,
) -> [M512; 8] {
let (mut a0, mut b0, mut c0, mut d0) = (a0, b0, c0, d0);
let (mut a1, mut b1, mut c1, mut d1) = (a1, b1, c1, d1);
g1(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
g2(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
diagonalize(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
g1(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
g2(
&mut a0, &mut b0, &mut c0, &mut d0, &mut a1, &mut b1, &mut c1, &mut d1,
);
undiagonalize(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
[a0, b0, c0, d0, a1, b1, c1, d1]
}
fn swap_halves(a0: &mut M512, a1: &mut M512) {
let t0 = shuffle_i64x2::<0b01_00_01_00>(*a0, *a1);
let t1 = shuffle_i64x2::<0b11_10_11_10>(*a0, *a1);
*a0 = t0;
*a1 = t1;
}
fn swap_quarters(a0: &mut M512, a1: &mut M512) {
swap_halves(a0, a1);
*a0 = permutexvar(QUARTER_IDX, *a0);
*a1 = permutexvar(QUARTER_IDX, *a1);
}
fn unswap_quarters(a0: &mut M512, a1: &mut M512) {
*a0 = permutexvar(QUARTER_IDX, *a0);
*a1 = permutexvar(QUARTER_IDX, *a1);
swap_halves(a0, a1);
}
#[allow(clippy::too_many_arguments)]
fn blake2_round_1(
a0: M512,
c0: M512,
b0: M512,
d0: M512,
a1: M512,
c1: M512,
b1: M512,
d1: M512,
) -> [M512; 8] {
let (mut a0, mut c0, mut b0, mut d0) = (a0, c0, b0, d0);
let (mut a1, mut c1, mut b1, mut d1) = (a1, c1, b1, d1);
swap_halves(&mut a0, &mut b0);
swap_halves(&mut c0, &mut d0);
swap_halves(&mut a1, &mut b1);
swap_halves(&mut c1, &mut d1);
let r = blake2_round(a0, b0, c0, d0, a1, b1, c1, d1);
a0 = r[0];
b0 = r[1];
c0 = r[2];
d0 = r[3];
a1 = r[4];
b1 = r[5];
c1 = r[6];
d1 = r[7];
swap_halves(&mut a0, &mut b0);
swap_halves(&mut c0, &mut d0);
swap_halves(&mut a1, &mut b1);
swap_halves(&mut c1, &mut d1);
[a0, c0, b0, d0, a1, c1, b1, d1]
}
#[allow(clippy::too_many_arguments)]
fn blake2_round_2(
a0: M512,
a1: M512,
b0: M512,
b1: M512,
c0: M512,
c1: M512,
d0: M512,
d1: M512,
) -> [M512; 8] {
let (mut a0, mut a1, mut b0, mut b1) = (a0, a1, b0, b1);
let (mut c0, mut c1, mut d0, mut d1) = (c0, c1, d0, d1);
swap_quarters(&mut a0, &mut a1);
swap_quarters(&mut b0, &mut b1);
swap_quarters(&mut c0, &mut c1);
swap_quarters(&mut d0, &mut d1);
let r = blake2_round(a0, b0, c0, d0, a1, b1, c1, d1);
a0 = r[0];
b0 = r[1];
c0 = r[2];
d0 = r[3];
a1 = r[4];
b1 = r[5];
c1 = r[6];
d1 = r[7];
unswap_quarters(&mut a0, &mut a1);
unswap_quarters(&mut b0, &mut b1);
unswap_quarters(&mut c0, &mut c1);
unswap_quarters(&mut d0, &mut d1);
[a0, a1, b0, b1, c0, c1, d0, d1]
}
let load = |b: &Block, j: usize| -> M512 { core::array::from_fn(|k| b.0[8 * j + k]) };
let mut state: [M512; BITS512_WORDS_IN_BLOCK] = core::array::from_fn(|j| load(prev, j));
let mut block_xy = [[0u64; 8]; BITS512_WORDS_IN_BLOCK];
if with_xor {
for i in 0..BITS512_WORDS_IN_BLOCK {
state[i] = xor(state[i], load(ref_block, i));
block_xy[i] = xor(state[i], load(next_block, i));
}
} else {
for i in 0..BITS512_WORDS_IN_BLOCK {
state[i] = xor(state[i], load(ref_block, i));
block_xy[i] = state[i];
}
}
for i in 0..2 {
let base = 8 * i;
let r = blake2_round_1(
state[base],
state[base + 1],
state[base + 2],
state[base + 3],
state[base + 4],
state[base + 5],
state[base + 6],
state[base + 7],
);
for (k, value) in r.into_iter().enumerate() {
state[base + k] = value;
}
}
for i in 0..2 {
let r = blake2_round_2(
state[i],
state[2 + i],
state[4 + i],
state[6 + i],
state[8 + i],
state[10 + i],
state[12 + i],
state[14 + i],
);
for (k, value) in r.into_iter().enumerate() {
state[2 * k + i] = value;
}
}
for i in 0..BITS512_WORDS_IN_BLOCK {
state[i] = xor(state[i], block_xy[i]);
next_block.0[8 * i..8 * i + 8].copy_from_slice(&state[i]);
}
}
#[cfg(test)]
mod tests {
use super::super::sse2::test_support::{
Group, XorShift64Star, assert_fill_block_matches_scalar, check_official_vectors,
skip_unless_available,
};
use super::*;
use crate::fill_block::{Backend, backend, detect, fill_segment_fn};
use crate::params::QWORDS_IN_BLOCK;
#[test]
fn quarter_permutation_is_an_involution() {
assert_eq!(QUARTER_IDX, [0, 1, 4, 5, 2, 3, 6, 7]);
for (k, &target) in QUARTER_IDX.iter().enumerate() {
let once = target as usize;
assert!(once < 8, "index {once} out of range");
assert_eq!(
QUARTER_IDX[once] as usize, k,
"QUARTER_IDX is not an involution at {k}"
);
}
}
fn swap_halves_on_indices(a0: &mut [usize; 8], a1: &mut [usize; 8]) {
let (o0, o1) = (*a0, *a1);
a0[0..4].copy_from_slice(&o0[0..4]);
a0[4..8].copy_from_slice(&o1[0..4]);
a1[0..4].copy_from_slice(&o0[4..8]);
a1[4..8].copy_from_slice(&o1[4..8]);
}
fn permute_on_indices(a: &mut [usize; 8]) {
let old = *a;
for k in 0..8 {
a[k] = old[QUARTER_IDX[k] as usize];
}
}
#[test]
fn swap_halves_is_an_involution() {
let mut a0 = [0, 1, 2, 3, 4, 5, 6, 7];
let mut a1 = [8, 9, 10, 11, 12, 13, 14, 15];
let (o0, o1) = (a0, a1);
swap_halves_on_indices(&mut a0, &mut a1);
assert_eq!(a0, [0, 1, 2, 3, 8, 9, 10, 11]);
assert_eq!(a1, [4, 5, 6, 7, 12, 13, 14, 15]);
swap_halves_on_indices(&mut a0, &mut a1);
assert_eq!((a0, a1), (o0, o1), "SWAP_HALVES must be its own inverse");
}
#[test]
fn swap_and_unswap_quarters_are_inverses() {
let mut a0 = [0, 1, 2, 3, 4, 5, 6, 7];
let mut a1 = [8, 9, 10, 11, 12, 13, 14, 15];
let (o0, o1) = (a0, a1);
swap_halves_on_indices(&mut a0, &mut a1);
permute_on_indices(&mut a0);
permute_on_indices(&mut a1);
assert_ne!((a0, a1), (o0, o1), "SWAP_QUARTERS must actually move data");
permute_on_indices(&mut a0);
permute_on_indices(&mut a1);
swap_halves_on_indices(&mut a0, &mut a1);
assert_eq!(
(a0, a1),
(o0, o1),
"UNSWAP_QUARTERS must undo SWAP_QUARTERS"
);
}
#[test]
fn diagonalize_immediates_are_the_right_rotations() {
fn apply(imm: u32, src: [usize; 4]) -> [usize; 4] {
let mut out = [0usize; 4];
for k in 0..4 {
out[k] = src[((imm >> (2 * k)) & 3) as usize];
}
out
}
const ROT1: u32 = 0b00_11_10_01;
const ROT2: u32 = 0b01_00_11_10;
const ROT3: u32 = 0b10_01_00_11;
let id = [0usize, 1, 2, 3];
assert_eq!(apply(ROT1, id), [1, 2, 3, 0], "B must rotate left by 1");
assert_eq!(apply(ROT2, id), [2, 3, 0, 1], "C must rotate left by 2");
assert_eq!(apply(ROT3, id), [3, 0, 1, 2], "D must rotate left by 3");
assert_eq!(apply(ROT3, apply(ROT1, id)), id, "B round trip");
assert_eq!(apply(ROT2, apply(ROT2, id)), id, "C round trip");
assert_eq!(apply(ROT1, apply(ROT3, id)), id, "D round trip");
}
#[test]
fn swap_halves_immediates_match_the_c() {
const fn mm_shuffle(z: u32, y: u32, x: u32, w: u32) -> u32 {
(z << 6) | (y << 4) | (x << 2) | w
}
assert_eq!(0b01_00_01_00, mm_shuffle(1, 0, 1, 0));
assert_eq!(0b11_10_11_10, mm_shuffle(3, 2, 3, 2));
}
#[test]
fn avx512_is_never_selected_on_a_host_without_it() {
if !Backend::Avx512.is_available() {
assert_ne!(
detect(),
Backend::Avx512,
"AVX-512F absent, so it must never be selected"
);
assert_ne!(backend(), Backend::Avx512, "nor cached");
} else {
assert_eq!(
detect(),
Backend::Avx512,
"AVX-512F present, so it is the top preference"
);
assert_eq!(backend(), Backend::Avx512);
}
#[cfg(feature = "std")]
assert_eq!(
Backend::Avx512.is_available(),
std::arch::is_x86_feature_detected!("avx512f")
);
}
#[test]
fn dispatch_resolves_to_this_module() {
let f = fill_segment_fn(Backend::Avx512);
assert!(
core::ptr::fn_addr_eq(f, fill_segment as unsafe fn(&Instance, Position)),
"fill_segment_fn(Avx512) must be avx512::fill_segment"
);
assert!(!core::ptr::fn_addr_eq(
f,
crate::fill_block::scalar::fill_segment as unsafe fn(&Instance, Position)
));
}
pub(super) mod sim {
pub type M512 = [u64; 8];
pub fn xor(a: M512, b: M512) -> M512 {
core::array::from_fn(|i| a[i] ^ b[i])
}
pub fn add(a: M512, b: M512) -> M512 {
core::array::from_fn(|i| a[i].wrapping_add(b[i]))
}
pub fn mul_epu32(a: M512, b: M512) -> M512 {
core::array::from_fn(|i| (a[i] & 0xFFFF_FFFF).wrapping_mul(b[i] & 0xFFFF_FFFF))
}
pub fn ror<const N: u32>(a: M512) -> M512 {
core::array::from_fn(|i| a[i].rotate_right(N))
}
pub fn permutex<const IMM: u32>(a: M512) -> M512 {
let mut out = [0u64; 8];
for half in 0..2 {
for j in 0..4 {
out[half * 4 + j] = a[half * 4 + (((IMM >> (2 * j)) & 3) as usize)];
}
}
out
}
pub fn permutexvar(idx: [i64; 8], a: M512) -> M512 {
core::array::from_fn(|j| a[(idx[j] as usize) & 7])
}
pub fn shuffle_i64x2<const IMM: u32>(a: M512, b: M512) -> M512 {
let lane = |v: M512, l: usize| [v[2 * l], v[2 * l + 1]];
let picked = [
lane(a, (IMM & 3) as usize),
lane(a, ((IMM >> 2) & 3) as usize),
lane(b, ((IMM >> 4) & 3) as usize),
lane(b, ((IMM >> 6) & 3) as usize),
];
let mut out = [0u64; 8];
for (l, pair) in picked.iter().enumerate() {
out[2 * l] = pair[0];
out[2 * l + 1] = pair[1];
}
out
}
}
#[test]
fn lane_algebra_matches_scalar_over_4096_triples() {
let mut rng = XorShift64Star::new(0x0123_4567_89AB_CDEF);
let mut compared = 0u32;
for iteration in 0..4096u32 {
let prev = rng.next_block();
let reference = rng.next_block();
let original_next = rng.next_block();
for with_xor in [false, true] {
let mut want = original_next;
crate::fill_block::scalar::fill_block(&prev, &reference, &mut want, with_xor);
let mut got = original_next;
super::simulated_fill_block(&prev, &reference, &mut got, with_xor);
for w in 0..QWORDS_IN_BLOCK {
assert_eq!(
got.0[w], want.0[w],
"avx512 lane algebra: iteration {iteration}, \
with_xor={with_xor}, word {w}"
);
}
compared += 1;
}
}
assert_eq!(compared, 4096 * 2);
}
#[test]
fn lane_algebra_keeps_all_zero_all_zero() {
let mut next = Block::ZERO;
super::simulated_fill_block(&Block::ZERO, &Block::ZERO, &mut next, false);
assert_eq!(next, Block::ZERO);
}
fn skip_without_avx512() -> bool {
skip_unless_available(Backend::Avx512)
}
#[target_feature(enable = "avx512f")]
unsafe fn fill_block_blocks(prev: &Block, reference: &Block, next: &mut Block, with_xor: bool) {
unsafe {
let mut state = [_mm512_setzero_si512(); BITS512_WORDS_IN_BLOCK];
let p = prev.as_ptr().cast::<__m512i>();
for (i, slot) in state.iter_mut().enumerate() {
*slot = _mm512_loadu_si512(p.add(i));
}
fill_block(&mut state, reference, next, with_xor);
}
}
#[test]
fn fill_block_matches_scalar_over_2048_triples() {
if skip_without_avx512() {
return;
}
unsafe {
assert_fill_block_matches_scalar(
"avx512",
fill_block_blocks,
2048,
0x0123_4567_89AB_CDEF,
);
}
}
#[test]
fn all_zero_stays_all_zero() {
if skip_without_avx512() {
return;
}
let mut next = Block::ZERO;
unsafe { fill_block_blocks(&Block::ZERO, &Block::ZERO, &mut next, false) };
assert_eq!(next, Block::ZERO);
}
#[test]
fn every_input_word_reaches_the_output() {
if skip_without_avx512() {
return;
}
let mut rng = XorShift64Star::new(0x1357_9BDF_2468_ACE0);
let prev = rng.next_block();
let reference = rng.next_block();
let base = {
let mut b = Block::ZERO;
unsafe { fill_block_blocks(&prev, &reference, &mut b, false) };
b
};
for w in 0..QWORDS_IN_BLOCK {
let mut flipped_prev = prev;
flipped_prev.0[w] ^= 1;
let mut got = Block::ZERO;
unsafe { fill_block_blocks(&flipped_prev, &reference, &mut got, false) };
assert_ne!(got, base, "flipping prev word {w} changed nothing");
}
}
#[test]
fn official_vectors_avx512_forced_v0x10_argon2i() {
if skip_without_avx512() {
return;
}
unsafe { check_official_vectors(Backend::Avx512, Group::V0x10Argon2i, false) };
}
#[test]
fn official_vectors_avx512_forced_v0x13_argon2i() {
if skip_without_avx512() {
return;
}
unsafe { check_official_vectors(Backend::Avx512, Group::V0x13Argon2i, false) };
}
#[test]
fn official_vectors_avx512_forced_v0x13_argon2id() {
if skip_without_avx512() {
return;
}
unsafe { check_official_vectors(Backend::Avx512, Group::V0x13Argon2id, false) };
}
#[test]
#[ignore = "TEST_LARGE_RAM: 1 GiB arena"]
fn official_vectors_with_the_avx512_backend_forced_including_large_ram() {
if skip_without_avx512() {
return;
}
unsafe { check_official_vectors(Backend::Avx512, Group::All, true) };
}
}