Skip to main content

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}