Coverage Report

Created: 2026-09-04 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/chacha20-0.10.2/src/backends/sse2.rs
Line
Count
Source
1
//! SSE2 backend.
2
3
#![allow(unsafe_op_in_unsafe_fn, reason = "needs triage")]
4
#![allow(clippy::cast_possible_truncation, reason = "needs triage")]
5
#![allow(clippy::cast_possible_wrap, reason = "needs triage")]
6
#![allow(clippy::cast_sign_loss, reason = "needs triage")]
7
#![allow(clippy::undocumented_unsafe_blocks, reason = "TODO")]
8
9
use crate::{Rounds, STATE_WORDS, Variant};
10
11
#[cfg(feature = "rng")]
12
use crate::ChaChaCore;
13
14
#[cfg(feature = "cipher")]
15
use crate::chacha::Block;
16
#[cfg(feature = "cipher")]
17
use cipher::{
18
    BlockSizeUser, ParBlocksSizeUser, StreamCipherBackend, StreamCipherClosure,
19
    consts::{U4, U64},
20
};
21
use core::marker::PhantomData;
22
23
#[cfg(target_arch = "x86")]
24
use core::arch::x86::*;
25
#[cfg(target_arch = "x86_64")]
26
use core::arch::x86_64::*;
27
28
const PAR_BLOCKS: usize = 4;
29
30
#[inline]
31
#[target_feature(enable = "sse2")]
32
#[cfg(feature = "cipher")]
33
pub(crate) unsafe fn inner<R, F, V>(state: &mut [u32; STATE_WORDS], f: F)
34
where
35
    R: Rounds,
36
    F: StreamCipherClosure<BlockSize = U64>,
37
    V: Variant,
38
{
39
    let mut backend = Backend::<R, V>::new(state);
40
    f.call(&mut backend);
41
    backend.save_ctr(state);
42
}
43
44
#[inline]
45
#[target_feature(enable = "sse2")]
46
#[cfg(feature = "rng")]
47
0
pub(crate) unsafe fn rng_inner<R, V>(core: &mut ChaChaCore<R, V>, buffer: &mut [u32; 64])
48
0
where
49
0
    R: Rounds,
50
0
    V: Variant,
51
{
52
0
    let mut backend = Backend::<R, V>::new(&core.state);
53
0
    backend.gen_ks_blocks(buffer);
54
0
    backend.save_ctr(&mut core.state);
55
0
}
Unexecuted instantiation: chacha20::backends::sse2::rng_inner::<chacha20::R12, chacha20::variants::Legacy>
Unexecuted instantiation: chacha20::backends::sse2::rng_inner::<_, _>
56
57
struct Backend<R: Rounds, V: Variant> {
58
    v: [__m128i; 4],
59
    _pd: PhantomData<(R, V)>,
60
}
61
62
impl<R: Rounds, V: Variant> Backend<R, V> {
63
0
    unsafe fn new(state: &[u32; STATE_WORDS]) -> Self {
64
0
        let state_ptr = state.as_ptr().cast::<__m128i>();
65
        Self {
66
0
            v: core::array::from_fn(|i| _mm_loadu_si128(state_ptr.add(i))),
Unexecuted instantiation: <chacha20::backends::sse2::Backend<chacha20::R12, chacha20::variants::Legacy>>::new::{closure#0}
Unexecuted instantiation: <chacha20::backends::sse2::Backend<_, _>>::new::{closure#0}
67
0
            _pd: PhantomData,
68
        }
69
0
    }
Unexecuted instantiation: <chacha20::backends::sse2::Backend<chacha20::R12, chacha20::variants::Legacy>>::new
Unexecuted instantiation: <chacha20::backends::sse2::Backend<_, _>>::new
70
71
0
    unsafe fn save_ctr(self, state: &mut [u32; STATE_WORDS]) {
72
0
        let state_ptr = state.as_mut_ptr().cast::<__m128i>();
73
0
        _mm_storeu_si128(state_ptr.add(3), self.v[3]);
74
0
    }
Unexecuted instantiation: <chacha20::backends::sse2::Backend<chacha20::R12, chacha20::variants::Legacy>>::save_ctr
Unexecuted instantiation: <chacha20::backends::sse2::Backend<_, _>>::save_ctr
75
}
76
77
#[cfg(feature = "cipher")]
78
impl<R: Rounds, V: Variant> BlockSizeUser for Backend<R, V> {
79
    type BlockSize = U64;
80
}
81
82
#[cfg(feature = "cipher")]
83
impl<R: Rounds, V: Variant> ParBlocksSizeUser for Backend<R, V> {
84
    type ParBlocksSize = U4;
85
}
86
87
#[cfg(feature = "cipher")]
88
impl<R: Rounds, V: Variant> StreamCipherBackend for Backend<R, V> {
89
    #[inline(always)]
90
    fn gen_ks_block(&mut self, block: &mut Block) {
91
        unsafe {
92
            let res = rounds::<R, V>(&self.v);
93
            self.v[3] = match size_of::<V::Counter>() {
94
                4 => _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, 1)),
95
                8 => _mm_add_epi64(self.v[3], _mm_set_epi64x(0, 1)),
96
                _ => unreachable!(),
97
            };
98
99
            let block_ptr = block.as_mut_ptr().cast::<__m128i>();
100
            for i in 0..4 {
101
                _mm_storeu_si128(block_ptr.add(i), res[0][i]);
102
            }
103
        }
104
    }
105
106
    #[inline(always)]
107
    fn gen_par_ks_blocks(&mut self, blocks: &mut cipher::ParBlocks<Self>) {
108
        unsafe {
109
            let res = rounds::<R, V>(&self.v);
110
            self.v[3] = match size_of::<V::Counter>() {
111
                4 => _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, PAR_BLOCKS as i32)),
112
                8 => _mm_add_epi64(self.v[3], _mm_set_epi64x(0, PAR_BLOCKS as i64)),
113
                _ => unreachable!(),
114
            };
115
116
            let blocks_ptr = blocks.as_mut_ptr().cast::<__m128i>();
117
            for block in 0..PAR_BLOCKS {
118
                for i in 0..4 {
119
                    _mm_storeu_si128(blocks_ptr.add(i + block * PAR_BLOCKS), res[block][i]);
120
                }
121
            }
122
        }
123
    }
124
}
125
126
#[cfg(feature = "rng")]
127
impl<R: Rounds, V: Variant> Backend<R, V> {
128
    #[inline(always)]
129
0
    fn gen_ks_blocks(&mut self, block: &mut [u32; 64]) {
130
        const _: () = assert!(4 * PAR_BLOCKS * size_of::<__m128i>() == size_of::<[u32; 64]>());
131
        unsafe {
132
0
            let res = rounds::<R, V>(&self.v);
133
0
            self.v[3] = _mm_add_epi64(self.v[3], _mm_set_epi64x(0, PAR_BLOCKS as i64));
134
135
0
            let blocks_ptr = block.as_mut_ptr().cast::<__m128i>();
136
0
            for block in 0..PAR_BLOCKS {
137
0
                for i in 0..4 {
138
0
                    _mm_storeu_si128(blocks_ptr.add(i + block * PAR_BLOCKS), res[block][i]);
139
0
                }
140
            }
141
        }
142
0
    }
Unexecuted instantiation: <chacha20::backends::sse2::Backend<chacha20::R12, chacha20::variants::Legacy>>::gen_ks_blocks
Unexecuted instantiation: <chacha20::backends::sse2::Backend<_, _>>::gen_ks_blocks
143
}
144
145
#[inline]
146
#[target_feature(enable = "sse2")]
147
0
unsafe fn rounds<R: Rounds, V: Variant>(v: &[__m128i; 4]) -> [[__m128i; 4]; PAR_BLOCKS] {
148
0
    let mut res = [*v; 4];
149
0
    for block in 1..PAR_BLOCKS {
150
0
        res[block][3] = match size_of::<V::Counter>() {
151
0
            4 => _mm_add_epi32(res[block][3], _mm_set_epi32(0, 0, 0, block as i32)),
152
0
            8 => _mm_add_epi64(res[block][3], _mm_set_epi64x(0, block as i64)),
153
0
            _ => unreachable!(),
154
        }
155
    }
156
157
0
    for _ in 0..R::COUNT {
158
0
        double_quarter_round(&mut res);
159
0
    }
160
161
0
    for block in 0..PAR_BLOCKS {
162
0
        for i in 0..3 {
163
0
            res[block][i] = _mm_add_epi32(res[block][i], v[i]);
164
0
        }
165
0
        let ctr = match size_of::<V::Counter>() {
166
0
            4 => _mm_add_epi32(v[3], _mm_set_epi32(0, 0, 0, block as i32)),
167
0
            8 => _mm_add_epi64(v[3], _mm_set_epi64x(0, block as i64)),
168
0
            _ => unreachable!(),
169
        };
170
0
        res[block][3] = _mm_add_epi32(res[block][3], ctr);
171
    }
172
173
0
    res
174
0
}
Unexecuted instantiation: chacha20::backends::sse2::rounds::<chacha20::R12, chacha20::variants::Legacy>
Unexecuted instantiation: chacha20::backends::sse2::rounds::<_, _>
175
176
#[inline]
177
#[target_feature(enable = "sse2")]
178
0
unsafe fn double_quarter_round(v: &mut [[__m128i; 4]; PAR_BLOCKS]) {
179
0
    add_xor_rot(v);
180
0
    rows_to_cols(v);
181
0
    add_xor_rot(v);
182
0
    cols_to_rows(v);
183
0
}
Unexecuted instantiation: chacha20::backends::sse2::double_quarter_round
Unexecuted instantiation: chacha20::backends::sse2::double_quarter_round
184
185
/// The goal of this function is to transform the state words from:
186
/// ```text
187
/// [a0, a1, a2, a3]    [ 0,  1,  2,  3]
188
/// [b0, b1, b2, b3] == [ 4,  5,  6,  7]
189
/// [c0, c1, c2, c3]    [ 8,  9, 10, 11]
190
/// [d0, d1, d2, d3]    [12, 13, 14, 15]
191
/// ```
192
///
193
/// to:
194
/// ```text
195
/// [a0, a1, a2, a3]    [ 0,  1,  2,  3]
196
/// [b1, b2, b3, b0] == [ 5,  6,  7,  4]
197
/// [c2, c3, c0, c1]    [10, 11,  8,  9]
198
/// [d3, d0, d1, d2]    [15, 12, 13, 14]
199
/// ```
200
///
201
/// so that we can apply [`add_xor_rot`] to the resulting columns, and have it compute the
202
/// "diagonal rounds" (as defined in RFC 7539) in parallel. In practice, this shuffle is
203
/// non-optimal: the last state word to be altered in `add_xor_rot` is `b`, so the shuffle
204
/// blocks on the result of `b` being calculated.
205
///
206
/// We can optimize this by observing that the four quarter rounds in `add_xor_rot` are
207
/// data-independent: they only access a single column of the state, and thus the order of
208
/// the columns does not matter. We therefore instead shuffle the other three state words,
209
/// to obtain the following equivalent layout:
210
/// ```text
211
/// [a3, a0, a1, a2]    [ 3,  0,  1,  2]
212
/// [b0, b1, b2, b3] == [ 4,  5,  6,  7]
213
/// [c1, c2, c3, c0]    [ 9, 10, 11,  8]
214
/// [d2, d3, d0, d1]    [14, 15, 12, 13]
215
/// ```
216
///
217
/// See https://github.com/sneves/blake2-avx2/pull/4 for additional details. The earliest
218
/// known occurrence of this optimization is in floodyberry's SSE4 ChaCha code from 2014:
219
/// - https://github.com/floodyberry/chacha-opt/blob/0ab65cb99f5016633b652edebaf3691ceb4ff753/chacha_blocks_ssse3-64.S#L639-L643
220
#[inline]
221
#[target_feature(enable = "sse2")]
222
0
unsafe fn rows_to_cols(blocks: &mut [[__m128i; 4]; PAR_BLOCKS]) {
223
0
    for [a, _, c, d] in blocks.iter_mut() {
224
0
        // c >>>= 32; d >>>= 64; a >>>= 96;
225
0
        *c = _mm_shuffle_epi32(*c, 0b_00_11_10_01); // _MM_SHUFFLE(0, 3, 2, 1)
226
0
        *d = _mm_shuffle_epi32(*d, 0b_01_00_11_10); // _MM_SHUFFLE(1, 0, 3, 2)
227
0
        *a = _mm_shuffle_epi32(*a, 0b_10_01_00_11); // _MM_SHUFFLE(2, 1, 0, 3)
228
0
    }
229
0
}
Unexecuted instantiation: chacha20::backends::sse2::rows_to_cols
Unexecuted instantiation: chacha20::backends::sse2::rows_to_cols
230
231
/// The goal of this function is to transform the state words from:
232
/// ```text
233
/// [a3, a0, a1, a2]    [ 3,  0,  1,  2]
234
/// [b0, b1, b2, b3] == [ 4,  5,  6,  7]
235
/// [c1, c2, c3, c0]    [ 9, 10, 11,  8]
236
/// [d2, d3, d0, d1]    [14, 15, 12, 13]
237
/// ```
238
///
239
/// to:
240
/// ```text
241
/// [a0, a1, a2, a3]    [ 0,  1,  2,  3]
242
/// [b0, b1, b2, b3] == [ 4,  5,  6,  7]
243
/// [c0, c1, c2, c3]    [ 8,  9, 10, 11]
244
/// [d0, d1, d2, d3]    [12, 13, 14, 15]
245
/// ```
246
///
247
/// reversing the transformation of [`rows_to_cols`].
248
#[inline]
249
#[target_feature(enable = "sse2")]
250
0
unsafe fn cols_to_rows(blocks: &mut [[__m128i; 4]; PAR_BLOCKS]) {
251
0
    for [a, _, c, d] in blocks.iter_mut() {
252
0
        // c <<<= 32; d <<<= 64; a <<<= 96;
253
0
        *c = _mm_shuffle_epi32(*c, 0b_10_01_00_11); // _MM_SHUFFLE(2, 1, 0, 3)
254
0
        *d = _mm_shuffle_epi32(*d, 0b_01_00_11_10); // _MM_SHUFFLE(1, 0, 3, 2)
255
0
        *a = _mm_shuffle_epi32(*a, 0b_00_11_10_01); // _MM_SHUFFLE(0, 3, 2, 1)
256
0
    }
257
0
}
Unexecuted instantiation: chacha20::backends::sse2::cols_to_rows
Unexecuted instantiation: chacha20::backends::sse2::cols_to_rows
258
259
#[inline]
260
#[target_feature(enable = "sse2")]
261
0
unsafe fn add_xor_rot(blocks: &mut [[__m128i; 4]; PAR_BLOCKS]) {
262
0
    for [a, b, c, d] in blocks.iter_mut() {
263
0
        // a += b; d ^= a; d <<<= (16, 16, 16, 16);
264
0
        *a = _mm_add_epi32(*a, *b);
265
0
        *d = _mm_xor_si128(*d, *a);
266
0
        *d = _mm_xor_si128(_mm_slli_epi32(*d, 16), _mm_srli_epi32(*d, 16));
267
0
268
0
        // c += d; b ^= c; b <<<= (12, 12, 12, 12);
269
0
        *c = _mm_add_epi32(*c, *d);
270
0
        *b = _mm_xor_si128(*b, *c);
271
0
        *b = _mm_xor_si128(_mm_slli_epi32(*b, 12), _mm_srli_epi32(*b, 20));
272
0
273
0
        // a += b; d ^= a; d <<<= (8, 8, 8, 8);
274
0
        *a = _mm_add_epi32(*a, *b);
275
0
        *d = _mm_xor_si128(*d, *a);
276
0
        *d = _mm_xor_si128(_mm_slli_epi32(*d, 8), _mm_srli_epi32(*d, 24));
277
0
278
0
        // c += d; b ^= c; b <<<= (7, 7, 7, 7);
279
0
        *c = _mm_add_epi32(*c, *d);
280
0
        *b = _mm_xor_si128(*b, *c);
281
0
        *b = _mm_xor_si128(_mm_slli_epi32(*b, 7), _mm_srli_epi32(*b, 25));
282
0
    }
283
0
}
Unexecuted instantiation: chacha20::backends::sse2::add_xor_rot
Unexecuted instantiation: chacha20::backends::sse2::add_xor_rot