argon2/memory.rs
1//! Views into Argon2 memory that can be processed in parallel.
2//!
3//! This module implements, with a combination of compile-time borrowing and runtime checking, the
4//! cooperative contract described in section 3.4 (Indexing) of RFC 9106:
5//!
6//! > To enable parallel block computation, we further partition the memory matrix into SL = 4
7//! > vertical slices. The intersection of a slice and a lane is called a segment, which has a
8//! > length of q/SL. Segments of the same slice can be computed in parallel and do not reference
9//! > blocks from each other. All other blocks can be referenced.
10
11#![allow(clippy::unnecessary_safety_comment)]
12
13use core::marker::PhantomData;
14use core::ptr::NonNull;
15
16#[cfg(feature = "parallel")]
17use rayon::iter::{IntoParallelIterator, ParallelIterator};
18
19use crate::{Block, SYNC_POINTS};
20
21/// Extension trait for Argon2 memory blocks.
22pub(crate) trait Memory<'a> {
23 /// Compute each Argon2 segment.
24 ///
25 /// By default computation is single threaded. Parallel computation can be enabled with the
26 /// `parallel` feature, in which case [rayon] is used to compute as many lanes in parallel as
27 /// possible.
28 fn for_each_segment<F>(&mut self, lanes: usize, f: F)
29 where
30 F: Fn(SegmentView<'_>, usize, usize) + Sync + Send;
31}
32
33impl Memory<'_> for &mut [Block] {
34 #[cfg(not(feature = "parallel"))]
35 fn for_each_segment<F>(&mut self, lanes: usize, f: F)
36 where
37 F: Fn(SegmentView<'_>, usize, usize) + Sync + Send,
38 {
39 let inner = MemoryInner::new(self, lanes);
40 for slice in 0..SYNC_POINTS {
41 for lane in 0..lanes {
42 // SAFETY: `self` exclusively borrows the blocks, and we sequentially process
43 // slices and segments.
44 let segment = unsafe { SegmentView::new(inner, slice, lane) };
45 f(segment, slice, lane);
46 }
47 }
48 }
49
50 #[cfg(feature = "parallel")]
51 fn for_each_segment<F>(&mut self, lanes: usize, f: F)
52 where
53 F: Fn(SegmentView<'_>, usize, usize) + Sync + Send,
54 {
55 let inner = MemoryInner::new(self, lanes);
56 for slice in 0..SYNC_POINTS {
57 (0..lanes).into_par_iter().for_each(|lane| {
58 // SAFETY: `self` exclusively borrows the blocks, we sequentially process slices,
59 // and we create exactly one segment view per lane in a slice.
60 let segment = unsafe { SegmentView::new(inner, slice, lane) };
61 f(segment, slice, lane);
62 });
63 }
64 }
65}
66
67/// Low-level pointer and metadata for an Argon2 memory region.
68#[derive(Clone, Copy)]
69struct MemoryInner<'a> {
70 blocks: NonNull<Block>,
71 block_count: usize,
72 lane_length: usize,
73 phantom: PhantomData<&'a mut Block>,
74}
75
76impl MemoryInner<'_> {
77 fn new(memory_blocks: &mut [Block], lanes: usize) -> Self {
78 let block_count = memory_blocks.len();
79 let lane_length = block_count / lanes;
80
81 // SAFETY: the pointer needs to be derived from a mutable reference because (later)
82 // mutating the blocks through a pointer derived from a shared reference would be UB.
83 let blocks = NonNull::from(memory_blocks);
84
85 MemoryInner {
86 blocks: blocks.cast(),
87 block_count,
88 lane_length,
89 phantom: PhantomData,
90 }
91 }
92
93 fn lane_of(&self, index: usize) -> usize {
94 index / self.lane_length
95 }
96
97 fn slice_of(&self, index: usize) -> usize {
98 index / (self.lane_length / SYNC_POINTS) % SYNC_POINTS
99 }
100}
101
102// SAFETY: private type, and just a pointer with some metadata.
103unsafe impl Send for MemoryInner<'_> {}
104
105// SAFETY: private type, and just a pointer with some metadata.
106unsafe impl Sync for MemoryInner<'_> {}
107
108/// A view into Argon2 memory for a particular segment (i.e. slice × lane).
109pub(crate) struct SegmentView<'a> {
110 inner: MemoryInner<'a>,
111 slice: usize,
112 lane: usize,
113}
114
115impl<'a> SegmentView<'a> {
116 /// Create a view into Argon2 memory for a particular segment (i.e. slice × lane).
117 ///
118 /// # Safety
119 ///
120 /// At any time, there can be at most one view for a given Argon2 segment. Additionally, all
121 /// concurrent segment views must be for the same slice.
122 unsafe fn new(inner: MemoryInner<'a>, slice: usize, lane: usize) -> Self {
123 SegmentView { inner, slice, lane }
124 }
125
126 /// Get a shared reference to a block.
127 ///
128 /// # Panics
129 ///
130 /// Panics if the index is out of bounds or if the desired block *could* be mutably aliased (if
131 /// it is on the current slice but on a different lane/segment).
132 pub fn get_block(&self, index: usize) -> &Block {
133 assert!(index < self.inner.block_count);
134 assert!(self.inner.lane_of(index) == self.lane || self.inner.slice_of(index) != self.slice);
135
136 // SAFETY: by construction, the base pointer is valid for reads, and we assert that the
137 // index is in bounds. We also assert that the index either lies on this lane, or is on
138 // another slice. Finally, we're the only view into this segment, and mutating through it
139 // requires `&mut self` and is restricted to blocks within the segment.
140 unsafe { self.inner.blocks.add(index).as_ref() }
141 }
142
143 /// Get a mutable reference to a block.
144 ///
145 /// # Panics
146 ///
147 /// Panics if the index is out of bounds or if the desired block lies outside this segment.
148 pub fn get_block_mut(&mut self, index: usize) -> &mut Block {
149 assert!(index < self.inner.block_count);
150 assert_eq!(self.inner.lane_of(index), self.lane);
151 assert_eq!(self.inner.slice_of(index), self.slice);
152
153 // SAFETY: by construction, the base pointer is valid for reads and writes, and we assert
154 // that the index is in bounds. We also assert that the index lies on this segment, and
155 // we're the only view for it, taking `&mut self`.
156 unsafe { self.inner.blocks.add(index).as_mut() }
157 }
158}