Skip to main content

commonware_cryptography/reed_solomon/engine/
engine_nosimd.rs

1use crate::reed_solomon::engine::{
2    tables::{self, Mul16, Skew},
3    utils, Engine, GfElement, ShardsRefMut, GF_MODULUS, SHARD_CHUNK_BYTES,
4};
5use core::iter::zip;
6
7// ======================================================================
8// NoSimd - PUBLIC
9
10/// Optimized [`Engine`] without SIMD.
11///
12/// [`NoSimd`] is a basic optimized engine which works on all CPUs.
13#[derive(Clone, Copy)]
14pub struct NoSimd {
15    mul16: &'static Mul16,
16    skew: &'static Skew,
17}
18
19impl NoSimd {
20    /// Creates new [`NoSimd`], initializing all [tables]
21    /// needed for encoding or decoding.
22    ///
23    /// Currently only difference between encoding/decoding is
24    /// [`LogWalsh`] (128 kiB) which is only needed for decoding.
25    ///
26    /// [`LogWalsh`]: crate::reed_solomon::engine::tables::LogWalsh
27    pub fn new() -> Self {
28        let mul16 = tables::get_mul16();
29        let skew = tables::get_skew();
30
31        Self { mul16, skew }
32    }
33}
34
35impl Engine for NoSimd {
36    fn fft(
37        &self,
38        data: &mut ShardsRefMut<'_>,
39        pos: usize,
40        size: usize,
41        truncated_size: usize,
42        skew_delta: usize,
43    ) {
44        self.fft_private(data, pos, size, truncated_size, skew_delta);
45    }
46
47    fn ifft(
48        &self,
49        data: &mut ShardsRefMut<'_>,
50        pos: usize,
51        size: usize,
52        truncated_size: usize,
53        skew_delta: usize,
54    ) {
55        self.ifft_private(data, pos, size, truncated_size, skew_delta);
56    }
57
58    fn mul(&self, x: &mut [[u8; SHARD_CHUNK_BYTES]], log_m: GfElement) {
59        let lut = &self.mul16[log_m as usize];
60
61        for x_chunk in x.iter_mut() {
62            let (x_lo, x_hi) = x_chunk.split_at_mut(SHARD_CHUNK_BYTES / 2);
63
64            for i in 0..SHARD_CHUNK_BYTES / 2 {
65                let lo = x_lo[i];
66                let hi = x_hi[i];
67                let prod = lut[0][usize::from(lo & 15)]
68                    ^ lut[1][usize::from(lo >> 4)]
69                    ^ lut[2][usize::from(hi & 15)]
70                    ^ lut[3][usize::from(hi >> 4)];
71                x_lo[i] = prod as u8;
72                x_hi[i] = (prod >> 8) as u8;
73            }
74        }
75    }
76}
77
78// ======================================================================
79// NoSimd - IMPL Default
80
81impl Default for NoSimd {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87// ======================================================================
88// NoSimd - PRIVATE
89
90impl NoSimd {
91    /// `x[] ^= y[] * log_m`
92    fn mul_add(
93        &self,
94        x: &mut [[u8; SHARD_CHUNK_BYTES]],
95        y: &[[u8; SHARD_CHUNK_BYTES]],
96        log_m: GfElement,
97    ) {
98        let lut = &self.mul16[log_m as usize];
99
100        for (x_chunk, y_chunk) in zip(x.iter_mut(), y.iter()) {
101            let (x_lo, x_hi) = x_chunk.split_at_mut(SHARD_CHUNK_BYTES / 2);
102            let (y_lo, y_hi) = y_chunk.split_at(SHARD_CHUNK_BYTES / 2);
103
104            for i in 0..SHARD_CHUNK_BYTES / 2 {
105                let lo = y_lo[i];
106                let hi = y_hi[i];
107                let prod = lut[0][usize::from(lo & 15)]
108                    ^ lut[1][usize::from(lo >> 4)]
109                    ^ lut[2][usize::from(hi & 15)]
110                    ^ lut[3][usize::from(hi >> 4)];
111                x_lo[i] ^= prod as u8;
112                x_hi[i] ^= (prod >> 8) as u8;
113            }
114        }
115    }
116}
117
118// ======================================================================
119// NoSimd - PRIVATE - FFT (fast Fourier transform)
120
121impl NoSimd {
122    // Partial butterfly, caller must do `GF_MODULUS` check with `xor`.
123    #[inline(always)]
124    fn fft_butterfly_partial(
125        &self,
126        x: &mut [[u8; SHARD_CHUNK_BYTES]],
127        y: &mut [[u8; SHARD_CHUNK_BYTES]],
128        log_m: GfElement,
129    ) {
130        self.mul_add(x, y, log_m);
131        utils::xor(y, x);
132    }
133
134    #[inline(always)]
135    fn fft_butterfly_two_layers(
136        &self,
137        data: &mut ShardsRefMut<'_>,
138        pos: usize,
139        dist: usize,
140        log_m01: GfElement,
141        log_m23: GfElement,
142        log_m02: GfElement,
143    ) {
144        let (s0, s1, s2, s3) = data.dist4_mut(pos, dist);
145
146        // FIRST LAYER
147
148        if log_m02 == GF_MODULUS {
149            utils::xor(s2, s0);
150            utils::xor(s3, s1);
151        } else {
152            self.fft_butterfly_partial(s0, s2, log_m02);
153            self.fft_butterfly_partial(s1, s3, log_m02);
154        }
155
156        // SECOND LAYER
157
158        if log_m01 == GF_MODULUS {
159            utils::xor(s1, s0);
160        } else {
161            self.fft_butterfly_partial(s0, s1, log_m01);
162        }
163
164        if log_m23 == GF_MODULUS {
165            utils::xor(s3, s2);
166        } else {
167            self.fft_butterfly_partial(s2, s3, log_m23);
168        }
169    }
170
171    #[inline(always)]
172    fn fft_private(
173        &self,
174        data: &mut ShardsRefMut<'_>,
175        pos: usize,
176        size: usize,
177        truncated_size: usize,
178        skew_delta: usize,
179    ) {
180        // TWO LAYERS AT TIME
181
182        let mut dist4 = size;
183        let mut dist = size >> 2;
184        while dist != 0 {
185            let mut r = 0;
186            while r < truncated_size {
187                let base = r + dist + skew_delta - 1;
188
189                let log_m01 = self.skew[base];
190                let log_m02 = self.skew[base + dist];
191                let log_m23 = self.skew[base + dist * 2];
192
193                for i in r..r + dist {
194                    self.fft_butterfly_two_layers(data, pos + i, dist, log_m01, log_m23, log_m02);
195                }
196
197                r += dist4;
198            }
199            dist4 = dist;
200            dist >>= 2;
201        }
202
203        // FINAL ODD LAYER
204
205        if dist4 == 2 {
206            let mut r = 0;
207            while r < truncated_size {
208                let log_m = self.skew[r + skew_delta];
209
210                let (x, y) = data.dist2_mut(pos + r, 1);
211
212                if log_m == GF_MODULUS {
213                    utils::xor(y, x);
214                } else {
215                    self.fft_butterfly_partial(x, y, log_m);
216                }
217
218                r += 2;
219            }
220        }
221    }
222}
223
224// ======================================================================
225// NoSimd - PRIVATE - IFFT (inverse fast Fourier transform)
226
227impl NoSimd {
228    // Partial butterfly, caller must do `GF_MODULUS` check with `xor`.
229    #[inline(always)]
230    fn ifft_butterfly_partial(
231        &self,
232        x: &mut [[u8; SHARD_CHUNK_BYTES]],
233        y: &mut [[u8; SHARD_CHUNK_BYTES]],
234        log_m: GfElement,
235    ) {
236        utils::xor(y, x);
237        self.mul_add(x, y, log_m);
238    }
239
240    #[inline(always)]
241    fn ifft_butterfly_two_layers(
242        &self,
243        data: &mut ShardsRefMut<'_>,
244        pos: usize,
245        dist: usize,
246        log_m01: GfElement,
247        log_m23: GfElement,
248        log_m02: GfElement,
249    ) {
250        let (s0, s1, s2, s3) = data.dist4_mut(pos, dist);
251
252        // FIRST LAYER
253
254        if log_m01 == GF_MODULUS {
255            utils::xor(s1, s0);
256        } else {
257            self.ifft_butterfly_partial(s0, s1, log_m01);
258        }
259
260        if log_m23 == GF_MODULUS {
261            utils::xor(s3, s2);
262        } else {
263            self.ifft_butterfly_partial(s2, s3, log_m23);
264        }
265
266        // SECOND LAYER
267
268        if log_m02 == GF_MODULUS {
269            utils::xor(s2, s0);
270            utils::xor(s3, s1);
271        } else {
272            self.ifft_butterfly_partial(s0, s2, log_m02);
273            self.ifft_butterfly_partial(s1, s3, log_m02);
274        }
275    }
276
277    #[inline(always)]
278    fn ifft_private(
279        &self,
280        data: &mut ShardsRefMut<'_>,
281        pos: usize,
282        size: usize,
283        truncated_size: usize,
284        skew_delta: usize,
285    ) {
286        // TWO LAYERS AT TIME
287
288        let mut dist = 1;
289        let mut dist4 = 4;
290        while dist4 <= size {
291            let mut r = 0;
292            while r < truncated_size {
293                let base = r + dist + skew_delta - 1;
294
295                let log_m01 = self.skew[base];
296                let log_m02 = self.skew[base + dist];
297                let log_m23 = self.skew[base + dist * 2];
298
299                for i in r..r + dist {
300                    self.ifft_butterfly_two_layers(data, pos + i, dist, log_m01, log_m23, log_m02);
301                }
302
303                r += dist4;
304            }
305            dist = dist4;
306            dist4 <<= 2;
307        }
308
309        // FINAL ODD LAYER
310
311        if dist < size {
312            let log_m = self.skew[dist + skew_delta - 1];
313            if log_m == GF_MODULUS {
314                utils::xor_within(data, pos + dist, pos, dist);
315            } else {
316                let (mut a, mut b) = data.split_at_mut(pos + dist);
317                for i in 0..dist {
318                    self.ifft_butterfly_partial(
319                        &mut a[pos + i], // data[pos + i]
320                        &mut b[i],       // data[pos + i + dist]
321                        log_m,
322                    );
323                }
324            }
325        }
326    }
327}
328
329// ======================================================================
330// TESTS
331
332// Engines are tested indirectly via roundtrip tests of HighRate and LowRate.
333
334#[cfg(test)]
335mod tests {
336    use crate::reed_solomon::engine::{Engine, Naive, NoSimd, SHARD_CHUNK_BYTES};
337    #[cfg(not(feature = "std"))]
338    use alloc::vec;
339    use rand::{Rng, RngExt as _, SeedableRng};
340    use rand_chacha::ChaCha8Rng;
341
342    #[test]
343    fn mul() {
344        let naive = Naive::default();
345        let nosimd = NoSimd::default();
346
347        let mut rng = ChaCha8Rng::from_seed([0; 32]);
348
349        for shard_chunks in 0..6 {
350            let mut data_nosimd = vec![[0; SHARD_CHUNK_BYTES]; shard_chunks];
351            rng.fill_bytes(data_nosimd.as_flattened_mut());
352            let mut data_naive = data_nosimd.clone();
353
354            let log_m = rng.random();
355
356            nosimd.mul(&mut data_nosimd, log_m);
357            naive.mul(&mut data_naive, log_m);
358
359            assert_eq!(data_nosimd, data_naive);
360        }
361    }
362}