use core::{arch::x86_64::*, ops::Range};
use super::cpu::HaveAvx512ForMlKem;
pub(crate) fn mlkem768_sample_poly_ntt_8x(
inputs: &[[u8; 40]; 8],
outputs: &mut [i16; 256 * 8],
fallback_fn: fn(&[[u8; 40]; 8], &mut [i16; 256 * 8]),
tail_fn: fn(&mut [u64; 25], &mut [i16]),
) {
match HaveAvx512ForMlKem::check() {
Some(proof) => unsafe { sample_poly_ntt_8x_avx512(inputs, outputs, tail_fn, proof) },
None => fallback_fn(inputs, outputs),
}
}
#[target_feature(enable = "avx512f,avx512bw,avx512vbmi,avx512vbmi2")]
unsafe fn sample_poly_ntt_8x_avx512(
inputs: &[[u8; 40]; 8],
outputs: &mut [i16; 256 * 8],
tail_fn: fn(&mut [u64; 25], &mut [i16]),
_proof: HaveAvx512ForMlKem,
) {
let mut keccak_states = [_mm512_setzero_si512(); 25];
keccak_states[20] = _mm512_set1_epi64(0x8000_0000_0000_0000_u64 as i64);
let word = |i: usize, range: Range<usize>| {
u64::from_le_bytes(inputs[i][range].try_into().unwrap()) as i64
};
for (i, state) in keccak_states[..5].iter_mut().enumerate() {
*state = _mm512_setr_epi64(
word(0, i * 8..(i + 1) * 8),
word(1, i * 8..(i + 1) * 8),
word(2, i * 8..(i + 1) * 8),
word(3, i * 8..(i + 1) * 8),
word(4, i * 8..(i + 1) * 8),
word(5, i * 8..(i + 1) * 8),
word(6, i * 8..(i + 1) * 8),
word(7, i * 8..(i + 1) * 8),
);
}
let mut left = [256; 8];
_sha3_keccak8_f1600(&mut keccak_states, &RC);
_squeeze_rate_and_reject(&keccak_states, outputs, &mut left);
_sha3_keccak8_f1600(&mut keccak_states, &RC);
_squeeze_rate_and_reject(&keccak_states, outputs, &mut left);
_sha3_keccak8_f1600(&mut keccak_states, &RC);
_squeeze_rate_and_reject(&keccak_states, outputs, &mut left);
for (i, (left, output)) in left
.iter()
.zip(outputs.as_chunks_mut::<256>().0.iter_mut())
.enumerate()
{
if *left > 0 {
let mut state = extract_state(&keccak_states, i);
tail_fn(&mut state, &mut output[256 - *left..]);
}
}
}
#[target_feature(enable = "avx512f")]
fn _sha3_keccak8_f1600(state: &mut [__m512i; 25], rc: &[u64; 24]) {
for round_constant in rc {
let mut c = [_mm512_setzero_si512(); 5];
for x in 0..5 {
c[x] = xor3(
xor3(state[x], state[x + 5], state[x + 10]),
state[x + 15],
state[x + 20],
);
}
let mut c_rotated = [_mm512_setzero_si512(); 5];
for x in 0..5 {
c_rotated[x] = rotate_left(c[x], 1);
}
let mut b = [_mm512_setzero_si512(); 25];
for x in 0..5 {
for y in 0..5 {
let lane = xor3(state[x + 5 * y], c[(x + 4) % 5], c_rotated[(x + 1) % 5]);
b[y + 5 * ((2 * x + 3 * y) % 5)] = rotate_left(lane, RHO[x][y]);
}
}
for x in 0..5 {
for y in 0..5 {
state[x + 5 * y] =
xor_andnot(b[x + 5 * y], b[(x + 1) % 5 + 5 * y], b[(x + 2) % 5 + 5 * y]);
}
}
state[0] = xor(state[0], _mm512_set1_epi64(*round_constant as i64));
}
}
static RHO: [[i32; 5]; 5] = [
[0, 36, 3, 41, 18],
[1, 44, 10, 45, 2],
[62, 6, 43, 15, 61],
[28, 55, 25, 21, 56],
[27, 20, 39, 8, 14],
];
const RC: [u64; 24] = [
0x00000000_00000001,
0x00000000_00008082,
0x80000000_0000808A,
0x80000000_80008000,
0x00000000_0000808B,
0x00000000_80000001,
0x80000000_80008081,
0x80000000_00008009,
0x00000000_0000008A,
0x00000000_00000088,
0x00000000_80008009,
0x00000000_8000000A,
0x00000000_8000808B,
0x80000000_0000008B,
0x80000000_00008089,
0x80000000_00008003,
0x80000000_00008002,
0x80000000_00000080,
0x00000000_0000800A,
0x80000000_8000000A,
0x80000000_80008081,
0x80000000_00008080,
0x00000000_80000001,
0x80000000_80008008,
];
#[inline]
#[target_feature(enable = "avx512f")]
fn xor(a: __m512i, b: __m512i) -> __m512i {
_mm512_xor_si512(a, b)
}
#[inline]
#[target_feature(enable = "avx512f")]
fn xor3(a: __m512i, b: __m512i, c: __m512i) -> __m512i {
_mm512_ternarylogic_epi64::<0x96>(a, b, c)
}
#[inline]
#[target_feature(enable = "avx512f")]
fn xor_andnot(a: __m512i, b: __m512i, c: __m512i) -> __m512i {
_mm512_ternarylogic_epi64::<0xd2>(a, b, c)
}
#[inline]
#[target_feature(enable = "avx512f")]
fn rotate_left(x: __m512i, n: i32) -> __m512i {
_mm512_or_si512(
_mm512_sllv_epi64(x, _mm512_set1_epi64(n as i64)),
_mm512_srlv_epi64(x, _mm512_set1_epi64((64 - n) as i64)),
)
}
#[target_feature(enable = "avx512f")]
fn extract_state(states: &[__m512i; 25], index: usize) -> [u64; 25] {
let mut r = [0u64; 25];
for (lane, out) in r.iter_mut().enumerate() {
let mut words = [0u64; 8];
unsafe { _mm512_storeu_si512(words.as_mut_ptr().cast(), states[lane]) };
*out = words[index];
}
r
}
#[target_feature(enable = "avx512f,avx512bw,avx512vbmi,avx512vbmi2")]
fn _squeeze_rate_and_reject(
state: &[__m512i; 25],
output: &mut [i16; 256 * 8],
left: &mut [usize; 8],
) {
let mut rate = [[0u64; 8]; RATE_WORDS];
for (lane, out) in rate.iter_mut().enumerate() {
unsafe { _mm512_storeu_si512(out.as_mut_ptr().cast(), state[lane]) };
}
let spread = unsafe { _mm512_loadu_si512(SPREAD.as_ptr().cast()) };
let q = _mm512_set1_epi16(Q as i16);
let twelve_bits = _mm512_set1_epi16(0x0fff);
let shifts = _mm512_set1_epi32(0x0004_0000);
let mut bytes = [0u8; RATE_BYTES + 64];
for (i, (left, poly)) in left
.iter_mut()
.zip(output.as_chunks_mut::<256>().0.iter_mut())
.enumerate()
{
for (lane, chunk) in bytes[..RATE_BYTES]
.as_chunks_mut::<8>()
.0
.iter_mut()
.enumerate()
{
chunk.copy_from_slice(&rate[lane][i].to_le_bytes());
}
let mut used = 256 - *left;
for offset in (0..RATE_BYTES).step_by(GROUP_BYTES) {
if used == 256 {
break;
}
let raw = unsafe { _mm512_loadu_si512(bytes[offset..].as_ptr().cast()) };
let candidates = _mm512_and_si512(
_mm512_srlv_epi16(_mm512_permutexvar_epi8(spread, raw), shifts),
twelve_bits,
);
let mut accept = _mm512_cmplt_epu16_mask(candidates, q);
let valid = ((RATE_BYTES - offset) / 3 * 2).min(GROUP_CANDIDATES);
if valid < GROUP_CANDIDATES {
accept &= (1u32 << valid) - 1;
}
let take = (accept.count_ones() as usize).min(256 - used);
let compacted = _mm512_maskz_compress_epi16(accept, candidates);
unsafe {
_mm512_mask_storeu_epi16(
poly[used..].as_mut_ptr(),
((1u64 << take) - 1) as u32,
compacted,
)
};
used += take;
}
*left = 256 - used;
}
}
const RATE_WORDS: usize = (1600 - 256) / 64;
const RATE_BYTES: usize = RATE_WORDS * 8;
const GROUP_CANDIDATES: usize = 32;
const GROUP_BYTES: usize = GROUP_CANDIDATES / 2 * 3;
static SPREAD: [u8; 64] = {
let mut r = [0u8; 64];
let mut k = 0;
while k < GROUP_CANDIDATES {
let low = match k % 2 {
0 => 3 * (k / 2),
_ => 3 * (k / 2) + 1,
};
r[k * 2] = low as u8;
r[k * 2 + 1] = (low + 1) as u8;
k += 1;
}
r
};
const Q: u16 = 3329;