use core::arch::x86_64::*;
use core::mem::MaybeUninit;
use crate::block::{Block, Instance, Position};
use crate::params::{ADDRESSES_IN_BLOCK, HWORDS_IN_BLOCK};
const ADDRESSES_IN_BLOCK_U32: u32 = ADDRESSES_IN_BLOCK as u32;
const R24: [u8; 32] = [
3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10, 3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10,
];
const R16: [u8; 32] = [
2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9, 2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9,
];
#[inline(always)]
unsafe fn rotr32(x: __m256i) -> __m256i {
unsafe { _mm256_shuffle_epi32::<0b10_11_00_01>(x) }
}
#[inline(always)]
unsafe fn rotr24(x: __m256i) -> __m256i {
unsafe { _mm256_shuffle_epi8(x, _mm256_loadu_si256(R24.as_ptr().cast())) }
}
#[inline(always)]
unsafe fn rotr16(x: __m256i) -> __m256i {
unsafe { _mm256_shuffle_epi8(x, _mm256_loadu_si256(R16.as_ptr().cast())) }
}
#[inline(always)]
unsafe fn rotr63(x: __m256i) -> __m256i {
unsafe { _mm256_xor_si256(_mm256_srli_epi64::<63>(x), _mm256_add_epi64(x, x)) }
}
#[inline(always)]
unsafe fn muladd(a: __m256i, b: __m256i) -> __m256i {
unsafe {
let ml = _mm256_mul_epu32(a, b);
let ml = _mm256_add_epi64(ml, ml);
_mm256_add_epi64(a, _mm256_add_epi64(b, ml))
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn g1(
a0: &mut __m256i,
a1: &mut __m256i,
b0: &mut __m256i,
b1: &mut __m256i,
c0: &mut __m256i,
c1: &mut __m256i,
d0: &mut __m256i,
d1: &mut __m256i,
) {
unsafe {
*a0 = muladd(*a0, *b0);
*d0 = _mm256_xor_si256(*d0, *a0);
*d0 = rotr32(*d0);
*c0 = muladd(*c0, *d0);
*b0 = _mm256_xor_si256(*b0, *c0);
*b0 = rotr24(*b0);
*a1 = muladd(*a1, *b1);
*d1 = _mm256_xor_si256(*d1, *a1);
*d1 = rotr32(*d1);
*c1 = muladd(*c1, *d1);
*b1 = _mm256_xor_si256(*b1, *c1);
*b1 = rotr24(*b1);
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn g2(
a0: &mut __m256i,
a1: &mut __m256i,
b0: &mut __m256i,
b1: &mut __m256i,
c0: &mut __m256i,
c1: &mut __m256i,
d0: &mut __m256i,
d1: &mut __m256i,
) {
unsafe {
*a0 = muladd(*a0, *b0);
*d0 = _mm256_xor_si256(*d0, *a0);
*d0 = rotr16(*d0);
*c0 = muladd(*c0, *d0);
*b0 = _mm256_xor_si256(*b0, *c0);
*b0 = rotr63(*b0);
*a1 = muladd(*a1, *b1);
*d1 = _mm256_xor_si256(*d1, *a1);
*d1 = rotr16(*d1);
*c1 = muladd(*c1, *d1);
*b1 = _mm256_xor_si256(*b1, *c1);
*b1 = rotr63(*b1);
}
}
#[inline(always)]
unsafe fn diagonalize_1(
b0: &mut __m256i,
b1: &mut __m256i,
c0: &mut __m256i,
c1: &mut __m256i,
d0: &mut __m256i,
d1: &mut __m256i,
) {
unsafe {
*b0 = _mm256_permute4x64_epi64::<0b00_11_10_01>(*b0);
*c0 = _mm256_permute4x64_epi64::<0b01_00_11_10>(*c0);
*d0 = _mm256_permute4x64_epi64::<0b10_01_00_11>(*d0);
*b1 = _mm256_permute4x64_epi64::<0b00_11_10_01>(*b1);
*c1 = _mm256_permute4x64_epi64::<0b01_00_11_10>(*c1);
*d1 = _mm256_permute4x64_epi64::<0b10_01_00_11>(*d1);
}
}
#[inline(always)]
unsafe fn undiagonalize_1(
b0: &mut __m256i,
b1: &mut __m256i,
c0: &mut __m256i,
c1: &mut __m256i,
d0: &mut __m256i,
d1: &mut __m256i,
) {
unsafe {
*b0 = _mm256_permute4x64_epi64::<0b10_01_00_11>(*b0);
*c0 = _mm256_permute4x64_epi64::<0b01_00_11_10>(*c0);
*d0 = _mm256_permute4x64_epi64::<0b00_11_10_01>(*d0);
*b1 = _mm256_permute4x64_epi64::<0b10_01_00_11>(*b1);
*c1 = _mm256_permute4x64_epi64::<0b01_00_11_10>(*c1);
*d1 = _mm256_permute4x64_epi64::<0b00_11_10_01>(*d1);
}
}
#[inline(always)]
unsafe fn diagonalize_2(
b0: &mut __m256i,
b1: &mut __m256i,
c0: &mut __m256i,
c1: &mut __m256i,
d0: &mut __m256i,
d1: &mut __m256i,
) {
unsafe {
let tmp1 = _mm256_blend_epi32::<0xCC>(*b0, *b1);
let tmp2 = _mm256_blend_epi32::<0x33>(*b0, *b1);
*b1 = _mm256_permute4x64_epi64::<0b10_11_00_01>(tmp1);
*b0 = _mm256_permute4x64_epi64::<0b10_11_00_01>(tmp2);
core::mem::swap(c0, c1);
let tmp1 = _mm256_blend_epi32::<0xCC>(*d0, *d1);
let tmp2 = _mm256_blend_epi32::<0x33>(*d0, *d1);
*d0 = _mm256_permute4x64_epi64::<0b10_11_00_01>(tmp1);
*d1 = _mm256_permute4x64_epi64::<0b10_11_00_01>(tmp2);
}
}
#[inline(always)]
unsafe fn undiagonalize_2(
b0: &mut __m256i,
b1: &mut __m256i,
c0: &mut __m256i,
c1: &mut __m256i,
d0: &mut __m256i,
d1: &mut __m256i,
) {
unsafe {
let tmp1 = _mm256_blend_epi32::<0xCC>(*b0, *b1);
let tmp2 = _mm256_blend_epi32::<0x33>(*b0, *b1);
*b0 = _mm256_permute4x64_epi64::<0b10_11_00_01>(tmp1);
*b1 = _mm256_permute4x64_epi64::<0b10_11_00_01>(tmp2);
core::mem::swap(c0, c1);
let tmp1 = _mm256_blend_epi32::<0x33>(*d0, *d1);
let tmp2 = _mm256_blend_epi32::<0xCC>(*d0, *d1);
*d0 = _mm256_permute4x64_epi64::<0b10_11_00_01>(tmp1);
*d1 = _mm256_permute4x64_epi64::<0b10_11_00_01>(tmp2);
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn blake2_round_1(
a0: __m256i,
a1: __m256i,
b0: __m256i,
b1: __m256i,
c0: __m256i,
c1: __m256i,
d0: __m256i,
d1: __m256i,
) -> [__m256i; 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 {
g1(
&mut a0, &mut a1, &mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1,
);
g2(
&mut a0, &mut a1, &mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1,
);
diagonalize_1(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
g1(
&mut a0, &mut a1, &mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1,
);
g2(
&mut a0, &mut a1, &mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1,
);
undiagonalize_1(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
}
[a0, a1, b0, b1, c0, c1, d0, d1]
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn blake2_round_2(
a0: __m256i,
a1: __m256i,
b0: __m256i,
b1: __m256i,
c0: __m256i,
c1: __m256i,
d0: __m256i,
d1: __m256i,
) -> [__m256i; 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 {
g1(
&mut a0, &mut a1, &mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1,
);
g2(
&mut a0, &mut a1, &mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1,
);
diagonalize_2(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
g1(
&mut a0, &mut a1, &mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1,
);
g2(
&mut a0, &mut a1, &mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1,
);
undiagonalize_2(&mut b0, &mut b1, &mut c0, &mut c1, &mut d0, &mut d1);
}
[a0, a1, b0, b1, c0, c1, d0, d1]
}
#[inline(always)]
unsafe fn fill_block(
state: &mut [__m256i; HWORDS_IN_BLOCK],
ref_block: *const Block,
next_block: *mut Block,
with_xor: bool,
) {
let refp = ref_block.cast::<__m256i>();
let nextp = next_block.cast::<__m256i>();
let mut block_xy: [MaybeUninit<__m256i>; HWORDS_IN_BLOCK] =
[const { MaybeUninit::uninit() }; HWORDS_IN_BLOCK];
unsafe {
if with_xor {
for i in 0..HWORDS_IN_BLOCK {
state[i] = _mm256_xor_si256(state[i], _mm256_loadu_si256(refp.add(i)));
block_xy[i] = MaybeUninit::new(_mm256_xor_si256(
state[i],
_mm256_loadu_si256(nextp.add(i).cast_const()),
));
}
} else {
for i in 0..HWORDS_IN_BLOCK {
state[i] = _mm256_xor_si256(state[i], _mm256_loadu_si256(refp.add(i)));
block_xy[i] = MaybeUninit::new(state[i]);
}
}
}
for i in 0..4 {
let base = 8 * i;
let r = unsafe {
blake2_round_1(
state[base],
state[base + 4],
state[base + 1],
state[base + 5],
state[base + 2],
state[base + 6],
state[base + 3],
state[base + 7],
)
};
state[base] = r[0];
state[base + 4] = r[1];
state[base + 1] = r[2];
state[base + 5] = r[3];
state[base + 2] = r[4];
state[base + 6] = r[5];
state[base + 3] = r[6];
state[base + 7] = r[7];
}
for i in 0..4 {
let r = unsafe {
blake2_round_2(
state[i],
state[4 + i],
state[8 + i],
state[12 + i],
state[16 + i],
state[20 + i],
state[24 + i],
state[28 + i],
)
};
for (k, value) in r.into_iter().enumerate() {
state[4 * k + i] = value;
}
}
unsafe {
for i in 0..HWORDS_IN_BLOCK {
state[i] = _mm256_xor_si256(state[i], block_xy[i].assume_init());
_mm256_storeu_si256(nextp.add(i), state[i]);
}
}
}
#[inline(always)]
unsafe fn next_addresses(address_block: *mut Block, input_block: *mut Block) {
let zero = unsafe { _mm256_setzero_si256() };
let mut zero_state = [zero; HWORDS_IN_BLOCK];
let mut zero2_state = [zero; HWORDS_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, 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
};
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::<__m256i>()
.cast_const();
let mut state = [_mm256_setzero_si256(); HWORDS_IN_BLOCK];
for (i, slot) in state.iter_mut().enumerate() {
*slot = _mm256_loadu_si256(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 pseudo_rand: u64 = if data_independent_addressing {
let slot = (i % ADDRESSES_IN_BLOCK_U32) as usize;
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 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;
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 = "avx2")]
pub unsafe fn fill_segment(instance: &Instance, position: Position) {
unsafe { fill_segment_impl(instance, position) }
}
#[cfg(test)]
mod tests {
#![allow(unexpected_cfgs)]
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;
#[target_feature(enable = "avx2")]
unsafe fn fill_block_blocks(prev: &Block, reference: &Block, next: &mut Block, with_xor: bool) {
unsafe {
let mut state = [_mm256_setzero_si256(); HWORDS_IN_BLOCK];
let p = prev.as_ptr().cast::<__m256i>();
for (i, slot) in state.iter_mut().enumerate() {
*slot = _mm256_loadu_si256(p.add(i));
}
fill_block(&mut state, reference, next, with_xor);
}
}
const FORCE_UNDETECTED_AVX2: bool = cfg!(argon2_force_avx2);
fn skip_without_avx2() -> bool {
if FORCE_UNDETECTED_AVX2 {
return false;
}
skip_unless_available(Backend::Avx2)
}
#[test]
fn fill_block_matches_scalar_over_2048_triples() {
if skip_without_avx2() {
return;
}
unsafe {
assert_fill_block_matches_scalar(
"avx2",
fill_block_blocks,
2048,
0x0123_4567_89AB_CDEF,
);
}
}
#[test]
fn all_zero_stays_all_zero() {
if skip_without_avx2() {
return;
}
let mut next = Block::ZERO;
unsafe { fill_block_blocks(&Block::ZERO, &Block::ZERO, &mut next, false) };
assert_eq!(next, Block::ZERO);
}
#[test]
fn aliasing_ref_and_next_is_safe_and_ignores_the_old_contents() {
if skip_without_avx2() {
return;
}
let mut rng = XorShift64Star::new(0xA5A5_5A5A_1234_9876);
let prev = rng.next_block();
let reference = rng.next_block();
let mut want = Block::ZERO;
crate::fill_block::scalar::fill_block(&prev, &reference, &mut want, false);
let mut aliased = reference;
unsafe {
avx2_fill_block_aliased(&prev, &mut aliased);
}
assert_eq!(aliased, want);
}
#[target_feature(enable = "avx2")]
unsafe fn avx2_fill_block_aliased(prev: &Block, block: &mut Block) {
unsafe {
let mut state = [_mm256_setzero_si256(); HWORDS_IN_BLOCK];
let p = prev.as_ptr().cast::<__m256i>();
for (i, slot) in state.iter_mut().enumerate() {
*slot = _mm256_loadu_si256(p.add(i));
}
let ptr = &raw mut *block;
fill_block(&mut state, ptr.cast_const(), ptr, false);
}
}
#[test]
fn every_input_word_reaches_the_output() {
if skip_without_avx2() {
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_avx2_forced_v0x10_argon2i() {
if skip_without_avx2() {
return;
}
unsafe { check_official_vectors(Backend::Avx2, Group::V0x10Argon2i, false) };
}
#[test]
fn official_vectors_avx2_forced_v0x13_argon2i() {
if skip_without_avx2() {
return;
}
unsafe { check_official_vectors(Backend::Avx2, Group::V0x13Argon2i, false) };
}
#[test]
fn official_vectors_avx2_forced_v0x13_argon2id() {
if skip_without_avx2() {
return;
}
unsafe { check_official_vectors(Backend::Avx2, Group::V0x13Argon2id, false) };
}
#[test]
#[ignore = "TEST_LARGE_RAM: 1 GiB arena"]
fn official_vectors_with_the_avx2_backend_forced_including_large_ram() {
if skip_without_avx2() {
return;
}
unsafe { check_official_vectors(Backend::Avx2, Group::All, true) };
}
#[test]
fn detection_selects_avx2_only_when_available_and_nothing_wider_is() {
let has_avx2 = Backend::Avx2.is_available();
let has_avx512 = Backend::Avx512.is_available();
if has_avx2 && !has_avx512 {
assert_eq!(
detect(),
Backend::Avx2,
"AVX2 present and no AVX-512, so AVX2 must win"
);
assert_eq!(
backend(),
Backend::Avx2,
"the cache must agree with detect()"
);
}
if !has_avx2 {
assert_ne!(
detect(),
Backend::Avx2,
"AVX2 absent, so it must never be selected"
);
assert_ne!(backend(), Backend::Avx2);
}
#[cfg(feature = "std")]
assert_eq!(has_avx2, std::arch::is_x86_feature_detected!("avx2"));
}
#[test]
fn dispatch_resolves_to_this_module() {
let f = fill_segment_fn(Backend::Avx2);
assert!(
core::ptr::fn_addr_eq(f, fill_segment as unsafe fn(&Instance, Position)),
"fill_segment_fn(Avx2) must be avx2::fill_segment"
);
assert!(!core::ptr::fn_addr_eq(
f,
crate::fill_block::scalar::fill_segment as unsafe fn(&Instance, Position)
));
}
#[cfg(argon2_force_avx2)]
mod forced {
use super::*;
use crate::params::{Algorithm, Memory, Params, TagLen, Version};
const _: () = assert!(
FORCE_UNDETECTED_AVX2,
"mod forced is gated on argon2_force_avx2 but FORCE_UNDETECTED_AVX2 is false"
);
#[test]
fn the_forced_flag_is_active_and_bypasses_detection() {
assert!(!skip_without_avx2(), "the AVX2 tests must not skip here");
if !Backend::Avx2.is_available() {
assert_ne!(
detect(),
Backend::Avx2,
"forcing must never make detect() select AVX2"
);
}
}
#[test]
fn fill_block_matches_scalar_over_4096_more_triples() {
unsafe {
assert_fill_block_matches_scalar(
"avx2-forced",
fill_block_blocks,
4096,
0xC0FF_EE00_1234_5678,
);
}
}
#[test]
fn hash_matches_scalar_and_sse2_across_a_parameter_sweep() {
#[rustfmt::skip]
const GRID: &[(u32, u32, u32)] = &[
(8, 1, 1), (8, 2, 1), (8, 3, 1),
(16, 1, 2), (16, 2, 2), (16, 3, 2),
(24, 1, 3), (24, 2, 3), (24, 3, 3),
(32, 1, 4), (32, 2, 4), (32, 3, 4),
(57, 1, 1), (57, 2, 1), (57, 2, 2), (57, 3, 3), (57, 2, 4),
(64, 1, 1), (64, 2, 1), (64, 2, 2), (64, 3, 3), (64, 2, 4),
(100, 2, 3),
(256, 2, 1), (256, 2, 3),
(1024, 2, 1), (1024, 2, 3),
];
const ALGORITHMS: [Algorithm; 3] =
[Algorithm::Argon2d, Algorithm::Argon2i, Algorithm::Argon2id];
const VERSIONS: [Version; 2] = [Version::V0x10, Version::V0x13];
let mut compared = 0u32;
for &(m_cost, t_cost, lanes) in GRID {
for algorithm in ALGORITHMS {
for version in VERSIONS {
assert!(
m_cost >= 8 * lanes,
"grid entry m={m_cost} p={lanes} violates m_cost >= 8 * lanes"
);
let params = Params::builder()
.memory(Memory::kib(u64::from(m_cost)))
.passes(t_cost)
.lanes(lanes)
.tag_len(TagLen::bytes(32))
.build()
.expect("grid params must be valid");
let run = |b: Backend| {
let mut out = [0u8; 32];
unsafe {
crate::core::hash_traced(
b,
algorithm,
version,
¶ms,
b"password",
b"somesalt",
&[],
&[],
&mut out,
None,
)
}
.expect("hash must succeed");
out
};
let want = run(Backend::Scalar);
assert_eq!(
run(Backend::Avx2),
want,
"avx2 differs from scalar: {algorithm:?} v{:#x} \
m={m_cost} t={t_cost} p={lanes}",
version.as_u32()
);
assert_eq!(
run(Backend::Sse2),
want,
"sse2 differs from scalar: {algorithm:?} v{:#x} \
m={m_cost} t={t_cost} p={lanes}",
version.as_u32()
);
compared += 1;
}
}
}
assert_eq!(
compared,
(GRID.len() * 6) as u32,
"grid did not run in full"
);
assert!(compared >= 150, "grid is too small to be meaningful");
}
}
}