/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 |