Skip to main content

commonware_cryptography/reed_solomon/engine/
engine_nosimd.rs

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