1
0
Fork 0
mirror of https://github.com/BLAKE3-team/BLAKE3 synced 2024-05-28 01:16:02 +02:00

move hash_chunks tests

This commit is contained in:
Jack O'Connor 2023-07-09 16:54:47 -07:00
parent 41ebf95fe2
commit 7827f5e836
5 changed files with 109 additions and 202 deletions

View File

@ -13,4 +13,5 @@ edition = "2021"
cfg-if = "1.0.0"
[dev-dependencies]
hex = "0.4.3"
reference_impl = { path = "../../reference_impl" }

View File

@ -587,6 +587,7 @@ unsafe fn hash_chunks_using_compress(
) {
debug_assert!(input_len > 0);
debug_assert!(input_len <= MAX_SIMD_DEGREE * CHUNK_LEN);
input_len = cmp::min(input_len, MAX_SIMD_DEGREE * CHUNK_LEN);
while input_len > 0 {
let mut chunk_len = cmp::min(input_len, CHUNK_LEN);
input_len -= chunk_len;
@ -757,14 +758,18 @@ const TRANSPOSED_STRIDE: usize = 2 * MAX_SIMD_DEGREE;
pub struct TransposedVectors([[u32; 2 * MAX_SIMD_DEGREE]; 8]);
impl TransposedVectors {
pub fn parent_node(&self, parent_index: usize) -> BlockBytes {
let mut bytes = [0u8; 64];
pub fn extract_cv(&self, cv_index: usize) -> CVBytes {
let mut words = [0u32; 8];
for word_index in 0..8 {
bytes[word_index * WORD_LEN..][..WORD_LEN]
.copy_from_slice(&self.0[word_index][2 * parent_index].to_le_bytes());
bytes[(word_index + 8) * WORD_LEN..][..WORD_LEN]
.copy_from_slice(&self.0[word_index][2 * parent_index + 1].to_le_bytes());
words[word_index] = self.0[word_index][cv_index];
}
le_bytes_from_words_32(&words)
}
pub fn extract_parent_node(&self, parent_index: usize) -> BlockBytes {
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(&self.extract_cv(parent_index / 2));
bytes[32..].copy_from_slice(&self.extract_cv(parent_index / 2 + 1));
bytes
}

View File

@ -204,4 +204,10 @@ mod test {
fn test_compress_vs_reference() {
crate::test::test_compress_vs_reference(compress);
}
// This is circular but do it anyway.
#[test]
fn test_hash_chunks_vs_portable() {
crate::test::test_hash_chunks_vs_portable(hash_chunks, DEGREE);
}
}

View File

@ -24,7 +24,6 @@ pub fn paint_test_input(buf: &mut [u8]) {
}
pub fn test_compress_vs_portable(compress_fn: CompressFn) {
let flags = KEYED_HASH;
for block_len in BLOCK_LENGTHS {
dbg!(block_len);
let mut block = [0; BLOCK_LEN];
@ -36,7 +35,7 @@ pub fn test_compress_vs_portable(compress_fn: CompressFn) {
block_len as u32,
&TEST_KEY,
counter,
flags,
KEYED_HASH,
);
let mut test_cv = TEST_KEY;
@ -47,7 +46,7 @@ pub fn test_compress_vs_portable(compress_fn: CompressFn) {
block_len as u32,
test_cv_ptr,
counter,
flags,
KEYED_HASH,
test_cv_ptr,
);
}
@ -58,7 +57,6 @@ pub fn test_compress_vs_portable(compress_fn: CompressFn) {
}
pub fn test_compress_vs_reference(compress_fn: CompressFn) {
let flags = CHUNK_START | CHUNK_END | ROOT | KEYED_HASH;
for block_len in BLOCK_LENGTHS {
dbg!(block_len);
let mut block = [0; BLOCK_LEN];
@ -72,9 +70,97 @@ pub fn test_compress_vs_reference(compress_fn: CompressFn) {
let mut test_cv = TEST_KEY;
unsafe {
let test_cv_ptr: *mut CVBytes = &mut test_cv;
compress_fn(&block, block_len as u32, test_cv_ptr, 0, flags, test_cv_ptr);
compress_fn(
&block,
block_len as u32,
test_cv_ptr,
0,
CHUNK_START | CHUNK_END | ROOT | KEYED_HASH,
test_cv_ptr,
);
}
assert_eq!(ref_hash, test_cv);
}
}
fn check_transposed_eq(output_a: &TransposedVectors, output_b: &TransposedVectors) {
let mut mismatch = false;
for cv_index in 0..2 * MAX_SIMD_DEGREE {
let cv_a = output_a.extract_cv(cv_index);
let cv_b = output_b.extract_cv(cv_index);
if cv_a == [0; 32] && cv_b == [0; 32] {
println!("CV {cv_index:2} empty");
} else if cv_a == cv_b {
println!("CV {cv_index:2} matches");
} else {
mismatch = true;
println!("CV {cv_index:2} mismatch:");
println!(" {}", hex::encode(cv_a));
println!(" {}", hex::encode(cv_b));
}
}
if mismatch {
panic!("transposed outputs are not equal");
}
assert_eq!(output_a, output_b, "just double check");
}
pub fn test_hash_chunks_vs_portable(hash_chunks_fn: HashChunksFn, degree: usize) {
assert!(degree <= MAX_SIMD_DEGREE);
let mut input = [0u8; 2 * MAX_SIMD_DEGREE * CHUNK_LEN];
paint_test_input(&mut input);
dbg!(degree * CHUNK_LEN);
for input_2_len in [
1,
degree * CHUNK_LEN / 3,
2 * degree * CHUNK_LEN / 3,
degree * CHUNK_LEN,
] {
dbg!(input_2_len);
let input1 = &input[..degree * CHUNK_LEN];
let input2 = &input[degree * CHUNK_LEN..][..input_2_len];
for initial_counter in INITIAL_COUNTERS {
// Make two calls, to test the output_column parameter.
let mut portable_output = TransposedVectors::default();
let (portable_left, portable_right) = portable_output.split(degree);
Implementation::portable().hash_chunks(
input1,
&TEST_KEY,
initial_counter,
KEYED_HASH,
portable_left,
);
Implementation::portable().hash_chunks(
input2,
&TEST_KEY,
initial_counter + degree as u64,
KEYED_HASH,
portable_right,
);
let mut test_output = TransposedVectors::default();
let (test_left, test_right) = test_output.split(degree);
unsafe {
hash_chunks_fn(
input1.as_ptr(),
input1.len(),
&TEST_KEY,
initial_counter,
KEYED_HASH,
test_left.ptr,
);
hash_chunks_fn(
input2.as_ptr(),
input2.len(),
&TEST_KEY,
initial_counter + degree as u64,
KEYED_HASH,
test_right.ptr,
);
}
check_transposed_eq(&portable_output, &test_output);
}
}
}

View File

@ -51,197 +51,6 @@ pub const TEST_CASES_MAX: usize = 100 * CHUNK_LEN;
pub const TEST_KEY: &CVBytes = b"whats the Elvish word for friend";
pub const TEST_KEY_WORDS: &CVWords = &guts::words_from_le_bytes_32(TEST_KEY);
// Test a few different initial counter values.
// - 0: The base case.
// - i32::MAX: *No* overflow. But carry bugs in tricky SIMD code can screw this up, if you XOR
// when you're supposed to ANDNOT...
// - u32::MAX: The low word of the counter overflows for all inputs except the first.
const INITIAL_COUNTERS: &[u64] = &[0, i32::MAX as u64, u32::MAX as u64];
// Paint the input with a repeating byte pattern. We use a cycle length of 251,
// because that's the largest prime number less than 256. This makes it
// unlikely to swapping any two adjacent input blocks or chunks will give the
// same answer.
pub fn paint_test_input(buf: &mut [u8]) {
for (i, b) in buf.iter_mut().enumerate() {
*b = (i % 251) as u8;
}
}
type HashManyFn<A> = unsafe fn(
inputs: &[&A],
key: &CVWords,
counter: u64,
increment_counter: IncrementCounter,
flags: u8,
flags_start: u8,
flags_end: u8,
out: &mut [u8],
);
// A shared helper function for platform-specific tests.
pub fn test_hash_many_fn(
hash_many_chunks_fn: HashManyFn<[u8; CHUNK_LEN]>,
hash_many_parents_fn: HashManyFn<[u8; 2 * OUT_LEN]>,
) {
for &counter in INITIAL_COUNTERS {
#[cfg(feature = "std")]
dbg!(counter);
// 31 (16 + 8 + 4 + 2 + 1) inputs
const NUM_INPUTS: usize = 31;
let mut input_buf = [0; CHUNK_LEN * NUM_INPUTS];
paint_test_input(&mut input_buf);
// First hash chunks.
let mut chunks = ArrayVec::<&[u8; CHUNK_LEN], NUM_INPUTS>::new();
for i in 0..NUM_INPUTS {
chunks.push(array_ref!(input_buf, i * CHUNK_LEN, CHUNK_LEN));
}
let mut portable_chunks_out = [0; NUM_INPUTS * OUT_LEN];
crate::portable::hash_many(
&chunks,
TEST_KEY_WORDS,
counter,
IncrementCounter::Yes,
crate::KEYED_HASH,
crate::CHUNK_START,
crate::CHUNK_END,
&mut portable_chunks_out,
);
let mut test_chunks_out = [0; NUM_INPUTS * OUT_LEN];
unsafe {
hash_many_chunks_fn(
&chunks[..],
TEST_KEY_WORDS,
counter,
IncrementCounter::Yes,
crate::KEYED_HASH,
crate::CHUNK_START,
crate::CHUNK_END,
&mut test_chunks_out,
);
}
for n in 0..NUM_INPUTS {
#[cfg(feature = "std")]
dbg!(n);
assert_eq!(
&portable_chunks_out[n * OUT_LEN..][..OUT_LEN],
&test_chunks_out[n * OUT_LEN..][..OUT_LEN]
);
}
// Then hash parents.
let mut parents = ArrayVec::<&[u8; 2 * OUT_LEN], NUM_INPUTS>::new();
for i in 0..NUM_INPUTS {
parents.push(array_ref!(input_buf, i * 2 * OUT_LEN, 2 * OUT_LEN));
}
let mut portable_parents_out = [0; NUM_INPUTS * OUT_LEN];
crate::portable::hash_many(
&parents,
TEST_KEY_WORDS,
counter,
IncrementCounter::No,
crate::KEYED_HASH | crate::PARENT,
0,
0,
&mut portable_parents_out,
);
let mut test_parents_out = [0; NUM_INPUTS * OUT_LEN];
unsafe {
hash_many_parents_fn(
&parents[..],
TEST_KEY_WORDS,
counter,
IncrementCounter::No,
crate::KEYED_HASH | crate::PARENT,
0,
0,
&mut test_parents_out,
);
}
for n in 0..NUM_INPUTS {
#[cfg(feature = "std")]
dbg!(n);
assert_eq!(
&portable_parents_out[n * OUT_LEN..][..OUT_LEN],
&test_parents_out[n * OUT_LEN..][..OUT_LEN]
);
}
}
}
// Both xof() and xof_xof() have this signature.
type HashChunksFn = unsafe fn(
input: *const u8,
input_len: usize,
key: *const u32,
initial_counter: u64,
counter_group: u64,
flags: u32,
transposed_output: *mut u32,
);
pub fn test_hash_chunks_fn(target_fn: HashChunksFn, degree: usize) {
assert!(degree <= MAX_SIMD_DEGREE);
let mut input = [0u8; 2 * MAX_SIMD_DEGREE * CHUNK_LEN];
paint_test_input(&mut input);
for test_degree in 1..=degree {
let input1 = &input[..test_degree * CHUNK_LEN];
let input2 = &input[test_degree * CHUNK_LEN..][..test_degree * CHUNK_LEN];
for &initial_counter in INITIAL_COUNTERS {
// Make two calls, to test the output_column parameter.
let mut test_output = TransposedVectors::default();
unsafe {
target_fn(
input1.as_ptr(),
input1.len(),
TEST_KEY_WORDS.as_ptr(),
initial_counter,
0,
crate::KEYED_HASH as u32,
test_output[0].as_mut_ptr(),
);
target_fn(
input2.as_ptr(),
input2.len(),
TEST_KEY_WORDS.as_ptr(),
initial_counter + test_degree as u64,
0,
crate::KEYED_HASH as u32,
test_output[0].as_mut_ptr().add(test_degree),
);
}
let mut portable_output = TransposedVectors::default();
unsafe {
crate::portable::hash_chunks(
input1.as_ptr(),
input1.len(),
TEST_KEY_WORDS.as_ptr(),
initial_counter,
0,
crate::KEYED_HASH as u32,
test_output[0].as_mut_ptr(),
);
crate::portable::hash_chunks(
input2.as_ptr(),
input2.len(),
TEST_KEY_WORDS.as_ptr(),
initial_counter + test_degree as u64,
0,
crate::KEYED_HASH as u32,
test_output[0].as_mut_ptr().add(test_degree),
);
}
assert_eq!(portable_output, test_output);
}
}
}
fn paint_transposed_input(input: &mut TransposedVectors) {
let mut val = 0;
for row in 0..8 {