1#[cfg(feature = "std")]
11extern crate std;
12
13#[cfg(all(test, feature = "std"))]
15use core::{
16 assert_eq,
17 assert_ne,
18};
19
20use crate::keccak_p;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum OptimizationLevel {
28 Reference,
30 Basic,
32 Advanced,
34 Maximum,
36}
37
38impl OptimizationLevel {
39 pub fn best_available() -> Self {
41 if cfg!(all(
42 target_arch = "x86_64",
43 feature = "asm",
44 target_feature = "avx512f",
45 not(cross_compile)
46 )) {
47 Self::Maximum
48 } else if cfg!(all(
49 target_arch = "x86_64",
50 feature = "asm",
51 target_feature = "avx2",
52 not(cross_compile)
53 )) {
54 Self::Advanced
55 } else if cfg!(all(
56 target_arch = "aarch64",
57 feature = "asm",
58 feature = "arm64_sha3",
59 target_feature = "sha3",
60 feature = "std",
61 not(target_os = "windows")
62 )) {
63 Self::Basic
64 } else {
65 Self::Reference
66 }
67 }
68
69 pub fn is_available(self) -> bool {
71 match self {
72 Self::Reference => true,
73 Self::Basic => cfg!(any(
74 all(
75 target_arch = "x86_64",
76 feature = "asm",
77 target_feature = "avx2",
78 not(cross_compile)
79 ),
80 all(
81 target_arch = "aarch64",
82 feature = "asm",
83 feature = "arm64_sha3",
84 target_feature = "sha3",
85 feature = "std",
86 not(target_os = "windows")
87 )
88 )),
89 Self::Advanced => cfg!(all(
90 target_arch = "x86_64",
91 feature = "asm",
92 target_feature = "avx2",
93 not(cross_compile)
94 )),
95 Self::Maximum => cfg!(all(
96 target_arch = "x86_64",
97 feature = "asm",
98 target_feature = "avx512f",
99 not(cross_compile)
100 )),
101 }
102 }
103}
104
105pub fn p1600_optimized(state: &mut [u64; 25], level: OptimizationLevel) {
110 match level {
111 OptimizationLevel::Reference => {
112 keccak_p(state, 24);
113 }
114 OptimizationLevel::Basic => {
115 #[cfg(all(
116 target_arch = "aarch64",
117 feature = "asm",
118 feature = "arm64_sha3",
119 target_feature = "sha3",
120 feature = "std",
121 not(target_os = "windows")
122 ))]
123 {
124 unsafe { crate::armv8::p1600_armv8_sha3_asm(state, 24) };
125 }
126 #[cfg(all(
127 target_arch = "x86_64",
128 feature = "asm",
129 target_feature = "avx2",
130 not(cross_compile)
131 ))]
132 {
133 unsafe { crate::x86::p1600_avx2(state) };
134 }
135 #[cfg(not(any(
136 all(
137 target_arch = "aarch64",
138 feature = "asm",
139 feature = "arm64_sha3",
140 target_feature = "sha3",
141 feature = "std",
142 not(target_os = "windows")
143 ),
144 all(
145 target_arch = "x86_64",
146 feature = "asm",
147 target_feature = "avx2",
148 not(cross_compile)
149 )
150 )))]
151 {
152 keccak_p(state, 24);
153 }
154 }
155 OptimizationLevel::Advanced => {
156 #[cfg(all(
157 target_arch = "x86_64",
158 feature = "asm",
159 target_feature = "avx2",
160 not(cross_compile)
161 ))]
162 {
163 unsafe { crate::x86::p1600_avx2(state) };
164 }
165 #[cfg(not(all(
166 target_arch = "x86_64",
167 feature = "asm",
168 target_feature = "avx2",
169 not(cross_compile)
170 )))]
171 {
172 keccak_p(state, 24);
173 }
174 }
175 OptimizationLevel::Maximum => {
176 #[cfg(all(
177 target_arch = "x86_64",
178 feature = "asm",
179 target_feature = "avx512f",
180 not(cross_compile)
181 ))]
182 {
183 unsafe { crate::x86::p1600_avx512(state) };
184 }
185 #[cfg(all(
186 target_arch = "x86_64",
187 feature = "asm",
188 target_feature = "avx2",
189 not(cross_compile)
190 ))]
191 {
192 unsafe { crate::x86::p1600_avx2(state) };
193 }
194 #[cfg(not(all(
195 target_arch = "x86_64",
196 feature = "asm",
197 any(target_feature = "avx2", target_feature = "avx512f"),
198 not(cross_compile)
199 )))]
200 {
201 keccak_p(state, 24);
202 }
203 }
204 }
205}
206
207pub fn fast_loop_absorb_optimized(
212 state: &mut [u64; 25],
213 data: &[u8],
214 level: OptimizationLevel,
215) -> usize {
216 match level {
217 OptimizationLevel::Reference => fast_loop_absorb_reference(state, data),
218 OptimizationLevel::Basic => {
219 #[cfg(all(
220 target_arch = "x86_64",
221 feature = "asm",
222 target_feature = "avx2",
223 not(cross_compile)
224 ))]
225 {
226 return unsafe { crate::x86::fast_loop_absorb_avx2(state, 1, data) };
227 }
228 #[cfg(not(all(
229 target_arch = "x86_64",
230 feature = "asm",
231 target_feature = "avx2",
232 not(cross_compile)
233 )))]
234 {
235 fast_loop_absorb_reference(state, data)
236 }
237 }
238 OptimizationLevel::Advanced => {
239 #[cfg(all(
240 target_arch = "x86_64",
241 feature = "asm",
242 target_feature = "avx2",
243 not(cross_compile)
244 ))]
245 {
246 return unsafe { crate::x86::fast_loop_absorb_avx2(state, 4, data) };
247 }
248 #[cfg(not(all(
249 target_arch = "x86_64",
250 feature = "asm",
251 target_feature = "avx2",
252 not(cross_compile)
253 )))]
254 {
255 fast_loop_absorb_reference(state, data)
256 }
257 }
258 OptimizationLevel::Maximum => {
259 #[cfg(all(
260 target_arch = "x86_64",
261 feature = "asm",
262 target_feature = "avx512f",
263 not(cross_compile)
264 ))]
265 {
266 return unsafe { crate::x86::fast_loop_absorb_avx512(state, 8, data) };
267 }
268 #[cfg(all(
269 target_arch = "x86_64",
270 feature = "asm",
271 target_feature = "avx2",
272 not(cross_compile),
273 not(target_feature = "avx512f")
274 ))]
275 {
276 return unsafe { crate::x86::fast_loop_absorb_avx2(state, 4, data) };
277 }
278 #[cfg(not(all(
279 target_arch = "x86_64",
280 feature = "asm",
281 any(target_feature = "avx2", target_feature = "avx512f"),
282 not(cross_compile)
283 )))]
284 {
285 fast_loop_absorb_reference(state, data)
286 }
287 }
288 }
289}
290
291fn fast_loop_absorb_reference(state: &mut [u64; 25], data: &[u8]) -> usize {
295 let mut offset = 0;
296 let lane_size = size_of::<u64>();
297
298 while offset + lane_size <= data.len() {
299 let value = u64::from_le_bytes([
300 data[offset],
301 data[offset + 1],
302 data[offset + 2],
303 data[offset + 3],
304 data[offset + 4],
305 data[offset + 5],
306 data[offset + 6],
307 data[offset + 7],
308 ]);
309 state[0] ^= value;
310
311 keccak_p(state, 24);
313 offset += lane_size;
314 }
315
316 offset
317}
318
319#[cfg(feature = "simd")]
324pub mod parallel {
325 #[cfg(keccak_portable_simd)]
326 mod batch {
327 use super::super::*;
328 use crate::advanced_simd;
329
330 pub fn p1600_parallel(states: &mut [[u64; 25]], level: OptimizationLevel) {
335 match level {
336 OptimizationLevel::Reference => {
337 for state in states.iter_mut() {
339 keccak_p(state, 24);
340 }
341 }
342 OptimizationLevel::Basic => {
343 for chunk in states.chunks_mut(2) {
345 if chunk.len() == 2 {
346 advanced_simd::parallel::p1600_parallel_2x(&mut [chunk[0], chunk[1]]);
347 } else {
348 keccak_p(&mut chunk[0], 24);
349 }
350 }
351 }
352 OptimizationLevel::Advanced => {
353 for chunk in states.chunks_mut(4) {
355 match chunk.len() {
356 4 => advanced_simd::parallel::p1600_parallel_4x(&mut [
357 chunk[0], chunk[1], chunk[2], chunk[3],
358 ]),
359 3 => {
360 advanced_simd::parallel::p1600_parallel_2x(&mut [
361 chunk[0], chunk[1],
362 ]);
363 keccak_p(&mut chunk[2], 24);
364 }
365 2 => advanced_simd::parallel::p1600_parallel_2x(&mut [
366 chunk[0], chunk[1],
367 ]),
368 1 => keccak_p(&mut chunk[0], 24),
369 _ => unreachable!(),
370 }
371 }
372 }
373 OptimizationLevel::Maximum => {
374 for chunk in states.chunks_mut(8) {
376 match chunk.len() {
377 8 => advanced_simd::parallel::p1600_parallel_8x(&mut [
378 chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5],
379 chunk[6], chunk[7],
380 ]),
381 7 => {
382 advanced_simd::parallel::p1600_parallel_4x(&mut [
383 chunk[0], chunk[1], chunk[2], chunk[3],
384 ]);
385 advanced_simd::parallel::p1600_parallel_2x(&mut [
386 chunk[4], chunk[5],
387 ]);
388 keccak_p(&mut chunk[6], 24);
389 }
390 6 => {
391 advanced_simd::parallel::p1600_parallel_4x(&mut [
392 chunk[0], chunk[1], chunk[2], chunk[3],
393 ]);
394 advanced_simd::parallel::p1600_parallel_2x(&mut [
395 chunk[4], chunk[5],
396 ]);
397 }
398 5 => {
399 advanced_simd::parallel::p1600_parallel_4x(&mut [
400 chunk[0], chunk[1], chunk[2], chunk[3],
401 ]);
402 keccak_p(&mut chunk[4], 24);
403 }
404 4 => advanced_simd::parallel::p1600_parallel_4x(&mut [
405 chunk[0], chunk[1], chunk[2], chunk[3],
406 ]),
407 3 => {
408 advanced_simd::parallel::p1600_parallel_2x(&mut [
409 chunk[0], chunk[1],
410 ]);
411 keccak_p(&mut chunk[2], 24);
412 }
413 2 => advanced_simd::parallel::p1600_parallel_2x(&mut [
414 chunk[0], chunk[1],
415 ]),
416 1 => keccak_p(&mut chunk[0], 24),
417 _ => unreachable!(),
418 }
419 }
420 }
421 }
422 }
423
424 pub fn fast_loop_absorb_parallel(
429 states: &mut [[u64; 25]],
430 data: &[u8],
431 level: OptimizationLevel,
432 ) -> usize {
433 match level {
434 OptimizationLevel::Reference => {
435 let mut min_offset = usize::MAX;
437 for state in states.iter_mut() {
438 let offset = fast_loop_absorb_reference(state, data);
439 min_offset = min_offset.min(offset);
440 }
441 min_offset
442 }
443 OptimizationLevel::Basic => {
444 let mut min_offset = usize::MAX;
446 for chunk in states.chunks_mut(2) {
447 if chunk.len() == 2 {
448 let offset =
449 advanced_simd::fast_loop_absorb_advanced(&mut chunk[0], data, 2);
450 min_offset = min_offset.min(offset);
451 } else {
452 let offset = fast_loop_absorb_reference(&mut chunk[0], data);
453 min_offset = min_offset.min(offset);
454 }
455 }
456 min_offset
457 }
458 OptimizationLevel::Advanced => {
459 let mut min_offset = usize::MAX;
461 for chunk in states.chunks_mut(4) {
462 match chunk.len() {
463 4 => {
464 let offset = advanced_simd::fast_loop_absorb_advanced(
465 &mut chunk[0],
466 data,
467 4,
468 );
469 min_offset = min_offset.min(offset);
470 }
471 _ => {
472 for state in chunk.iter_mut() {
473 let offset = fast_loop_absorb_reference(state, data);
474 min_offset = min_offset.min(offset);
475 }
476 }
477 }
478 }
479 min_offset
480 }
481 OptimizationLevel::Maximum => {
482 let mut min_offset = usize::MAX;
484 for chunk in states.chunks_mut(8) {
485 match chunk.len() {
486 8 => {
487 let offset = advanced_simd::fast_loop_absorb_advanced(
488 &mut chunk[0],
489 data,
490 8,
491 );
492 min_offset = min_offset.min(offset);
493 }
494 _ => {
495 for state in chunk.iter_mut() {
496 let offset = fast_loop_absorb_reference(state, data);
497 min_offset = min_offset.min(offset);
498 }
499 }
500 }
501 }
502 min_offset
503 }
504 }
505 }
506 }
507
508 #[cfg(keccak_portable_simd)]
509 pub use batch::{
510 fast_loop_absorb_parallel,
511 p1600_parallel,
512 };
513
514 #[cfg(feature = "multithreading")]
515 use super::{
516 OptimizationLevel,
517 keccak_p,
518 };
519
520 #[cfg(all(feature = "multithreading", feature = "std"))]
525 pub fn p1600_multithreaded(
526 states: &[[u64; 25]],
527 level: OptimizationLevel,
528 ) -> Result<Vec<[u64; 25]>, Box<dyn std::error::Error + Send + Sync>> {
529 use crate::multithreading::process_keccak_states_global;
530
531 if let Ok(results) = process_keccak_states_global(states, level) {
533 Ok(results)
534 } else {
535 let mut results = Vec::with_capacity(states.len());
537 for state in states {
538 let mut state_copy = *state;
539 keccak_p(&mut state_copy, 24);
540 results.push(state_copy);
541 }
542 Ok(results)
543 }
544 }
545
546 #[cfg(all(feature = "multithreading", not(feature = "std"), feature = "alloc"))]
548 pub fn p1600_multithreaded(
549 states: &[[u64; 25]],
550 level: OptimizationLevel,
551 ) -> Result<alloc::vec::Vec<[u64; 25]>, alloc::boxed::Box<dyn core::error::Error + Send + Sync>>
552 {
553 extern crate alloc;
554 use crate::multithreading::process_keccak_states_global;
555
556 if let Ok(results) = process_keccak_states_global(states, level) {
558 Ok(results)
559 } else {
560 let mut results = alloc::vec::Vec::with_capacity(states.len());
562 for state in states {
563 let mut result_state = *state;
564 match level {
565 OptimizationLevel::Reference => {
566 keccak_p(&mut result_state, 24);
567 }
568 OptimizationLevel::Basic => {
569 #[cfg(all(
570 target_arch = "x86_64",
571 feature = "asm",
572 target_feature = "avx2"
573 ))]
574 unsafe {
575 crate::x86::p1600_avx2(&mut result_state);
576 }
577 #[cfg(not(all(
578 target_arch = "x86_64",
579 target_feature = "avx2",
580 not(cross_compile)
581 )))]
582 {
583 keccak_p(&mut result_state, 24);
584 }
585 }
586 OptimizationLevel::Advanced => {
587 #[cfg(all(
588 target_arch = "x86_64",
589 feature = "asm",
590 target_feature = "avx2"
591 ))]
592 unsafe {
593 crate::x86::p1600_avx2(&mut result_state);
594 }
595 #[cfg(not(all(
596 target_arch = "x86_64",
597 target_feature = "avx2",
598 not(cross_compile)
599 )))]
600 {
601 keccak_p(&mut result_state, 24);
602 }
603 }
604 OptimizationLevel::Maximum => {
605 #[cfg(all(
606 target_arch = "x86_64",
607 feature = "asm",
608 target_feature = "avx512f"
609 ))]
610 unsafe {
611 crate::x86::p1600_avx512(&mut result_state);
612 }
613 #[cfg(all(
614 target_arch = "x86_64",
615 feature = "asm",
616 target_feature = "avx2",
617 not(target_feature = "avx512f")
618 ))]
619 unsafe {
620 crate::x86::p1600_avx2(&mut result_state);
621 }
622 #[cfg(not(all(
623 target_arch = "x86_64",
624 any(target_feature = "avx2", target_feature = "avx512f")
625 )))]
626 {
627 keccak_p(&mut result_state, 24);
628 }
629 }
630 }
631 results.push(result_state);
632 }
633 Ok(results)
634 }
635 }
636}
637
638#[cfg(test)]
639#[allow(clippy::unreadable_literal)] mod tests {
641 #[cfg(feature = "std")]
642 use super::*;
643
644 #[test]
645 #[cfg(feature = "std")]
646 fn test_optimization_level_availability() {
647 assert!(OptimizationLevel::Reference.is_available());
649
650 let best = OptimizationLevel::best_available();
652 assert!(best.is_available());
653 }
654
655 #[test]
656 #[cfg(feature = "std")]
657 fn test_p1600_optimized_consistency() {
658 let mut state1 = [0u64; 25];
659 let mut state2 = [0u64; 25];
660
661 state1[0] = 0x1234567890ABCDEF;
663 state2[0] = 0x1234567890ABCDEF;
664
665 p1600_optimized(&mut state1, OptimizationLevel::Reference);
667 keccak_p(&mut state2, 24);
668
669 assert_eq!(state1, state2);
671 }
672
673 #[test]
674 #[cfg(feature = "std")]
675 fn test_fast_loop_absorb_optimized() {
676 let mut state = [0u64; 25];
677 let data = b"Hello, World! This is a test message for optimized absorption.";
678
679 let offset = fast_loop_absorb_optimized(&mut state, data, OptimizationLevel::Reference);
680
681 assert!(offset > 0);
683 assert_ne!(state[0], 0);
684 }
685
686 #[test]
687 #[cfg(all(feature = "std", feature = "simd", keccak_portable_simd))]
688 fn test_parallel_processing() {
689 use super::{
690 OptimizationLevel,
691 parallel,
692 };
693
694 let mut states = [[0u64; 25], [0u64; 25], [0u64; 25], [0u64; 25]];
695
696 for (i, state) in states.iter_mut().enumerate() {
698 state[0] = 0x1234567890ABCDEF + i as u64;
699 state[1] = 0xFEDCBA0987654321 + i as u64;
700 }
701
702 let mut original_states = [[0u64; 25], [0u64; 25], [0u64; 25], [0u64; 25]];
704 #[allow(clippy::needless_range_loop)]
705 for i in 0..states.len() {
706 for j in 0..25 {
707 original_states[i][j] = states[i][j];
708 }
709 }
710
711 parallel::p1600_parallel(&mut states, OptimizationLevel::Basic);
713
714 let mut any_changed = false;
717 for i in 0..states.len() {
718 for j in 0..25 {
719 if states[i][j] != original_states[i][j] {
720 any_changed = true;
721 break;
722 }
723 }
724 if any_changed {
725 break;
726 }
727 }
728
729 if !any_changed {
732 return;
734 }
735
736 for i in 0..states.len() {
738 let mut state_changed = false;
740 for j in 0..25 {
741 if states[i][j] != original_states[i][j] {
742 state_changed = true;
743 break;
744 }
745 }
746 assert!(
747 state_changed,
748 "State {} should have been modified by parallel processing",
749 i
750 );
751 }
752 }
753}