Skip to main content

commonware_cryptography/reed_solomon/engine/
engine_ssse3.rs

1use crate::reed_solomon::engine::{
2    tables::{self, Mul128, Multiply128lutT, Skew},
3    utils, Engine, GfElement, ShardsRefMut, GF_MODULUS, GF_ORDER, SHARD_CHUNK_BYTES,
4};
5#[cfg(target_arch = "x86")]
6use core::arch::x86::*;
7#[cfg(target_arch = "x86_64")]
8use core::arch::x86_64::*;
9use core::iter::zip;
10
11// ======================================================================
12// Ssse3 - PUBLIC
13
14/// Optimized [`Engine`] using SSSE3 instructions.
15///
16/// [`Ssse3`] is an optimized engine that follows the same algorithm as
17/// [`NoSimd`] but takes advantage of the x86 SSSE3 SIMD instructions.
18///
19/// [`NoSimd`]: crate::reed_solomon::engine::NoSimd
20#[derive(Clone, Copy)]
21pub struct Ssse3 {
22    mul128: &'static Mul128,
23    skew: &'static Skew,
24}
25
26impl Ssse3 {
27    /// Creates new [`Ssse3`], initializing all [tables]
28    /// needed for encoding or decoding.
29    ///
30    /// Currently only difference between encoding/decoding is
31    /// [`LogWalsh`] (128 kiB) which is only needed for decoding.
32    ///
33    /// [`LogWalsh`]: crate::reed_solomon::engine::tables::LogWalsh
34    pub fn new() -> Self {
35        cpufeatures::new!(has_ssse3_for_engine, "ssse3");
36        assert!(has_ssse3_for_engine::get());
37
38        let mul128 = tables::get_mul128();
39        let skew = tables::get_skew();
40
41        Self { mul128, skew }
42    }
43}
44
45impl Engine for Ssse3 {
46    fn fft(
47        &self,
48        data: &mut ShardsRefMut<'_>,
49        pos: usize,
50        size: usize,
51        truncated_size: usize,
52        skew_delta: usize,
53    ) {
54        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
55        unsafe {
56            self.fft_private_ssse3(data, pos, size, truncated_size, skew_delta);
57        }
58    }
59
60    fn ifft(
61        &self,
62        data: &mut ShardsRefMut<'_>,
63        pos: usize,
64        size: usize,
65        truncated_size: usize,
66        skew_delta: usize,
67    ) {
68        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
69        unsafe {
70            self.ifft_private_ssse3(data, pos, size, truncated_size, skew_delta);
71        }
72    }
73
74    fn mul(&self, x: &mut [[u8; SHARD_CHUNK_BYTES]], log_m: GfElement) {
75        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
76        unsafe {
77            self.mul_ssse3(x, log_m);
78        }
79    }
80
81    fn eval_poly(erasures: &mut [GfElement; GF_ORDER], truncated_size: usize) {
82        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
83        unsafe { Self::eval_poly_ssse3(erasures, truncated_size) }
84    }
85}
86
87// ======================================================================
88// Ssse3 - IMPL Default
89
90impl Default for Ssse3 {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96// ======================================================================
97// Ssse3 - PRIVATE
98
99impl Ssse3 {
100    #[target_feature(enable = "ssse3")]
101    unsafe fn mul_ssse3(&self, x: &mut [[u8; SHARD_CHUNK_BYTES]], log_m: GfElement) {
102        let lut = &self.mul128[log_m as usize];
103
104        for chunk in x.iter_mut() {
105            let x_ptr = chunk.as_mut_ptr().cast::<__m128i>();
106            // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
107            unsafe {
108                let x0_lo = _mm_loadu_si128(x_ptr);
109                let x1_lo = _mm_loadu_si128(x_ptr.add(1));
110                let x0_hi = _mm_loadu_si128(x_ptr.add(2));
111                let x1_hi = _mm_loadu_si128(x_ptr.add(3));
112                let (prod0_lo, prod0_hi) = Self::mul_128(x0_lo, x0_hi, lut);
113                let (prod1_lo, prod1_hi) = Self::mul_128(x1_lo, x1_hi, lut);
114                _mm_storeu_si128(x_ptr, prod0_lo);
115                _mm_storeu_si128(x_ptr.add(1), prod1_lo);
116                _mm_storeu_si128(x_ptr.add(2), prod0_hi);
117                _mm_storeu_si128(x_ptr.add(3), prod1_hi);
118            }
119        }
120    }
121
122    // Implementation of LEO_MUL_128
123    #[inline(always)]
124    fn mul_128(value_lo: __m128i, value_hi: __m128i, lut: &Multiply128lutT) -> (__m128i, __m128i) {
125        let mut prod_lo: __m128i;
126        let mut prod_hi: __m128i;
127
128        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
129        unsafe {
130            let t0_lo = _mm_loadu_si128(core::ptr::from_ref::<u128>(&lut.lo[0]).cast::<__m128i>());
131            let t1_lo = _mm_loadu_si128(core::ptr::from_ref::<u128>(&lut.lo[1]).cast::<__m128i>());
132            let t2_lo = _mm_loadu_si128(core::ptr::from_ref::<u128>(&lut.lo[2]).cast::<__m128i>());
133            let t3_lo = _mm_loadu_si128(core::ptr::from_ref::<u128>(&lut.lo[3]).cast::<__m128i>());
134
135            let t0_hi = _mm_loadu_si128(core::ptr::from_ref::<u128>(&lut.hi[0]).cast::<__m128i>());
136            let t1_hi = _mm_loadu_si128(core::ptr::from_ref::<u128>(&lut.hi[1]).cast::<__m128i>());
137            let t2_hi = _mm_loadu_si128(core::ptr::from_ref::<u128>(&lut.hi[2]).cast::<__m128i>());
138            let t3_hi = _mm_loadu_si128(core::ptr::from_ref::<u128>(&lut.hi[3]).cast::<__m128i>());
139
140            let clr_mask = _mm_set1_epi8(0x0f);
141
142            let data_0 = _mm_and_si128(value_lo, clr_mask);
143            prod_lo = _mm_shuffle_epi8(t0_lo, data_0);
144            prod_hi = _mm_shuffle_epi8(t0_hi, data_0);
145
146            let data_1 = _mm_and_si128(_mm_srli_epi64(value_lo, 4), clr_mask);
147            prod_lo = _mm_xor_si128(prod_lo, _mm_shuffle_epi8(t1_lo, data_1));
148            prod_hi = _mm_xor_si128(prod_hi, _mm_shuffle_epi8(t1_hi, data_1));
149
150            let data_0 = _mm_and_si128(value_hi, clr_mask);
151            prod_lo = _mm_xor_si128(prod_lo, _mm_shuffle_epi8(t2_lo, data_0));
152            prod_hi = _mm_xor_si128(prod_hi, _mm_shuffle_epi8(t2_hi, data_0));
153
154            let data_1 = _mm_and_si128(_mm_srli_epi64(value_hi, 4), clr_mask);
155            prod_lo = _mm_xor_si128(prod_lo, _mm_shuffle_epi8(t3_lo, data_1));
156            prod_hi = _mm_xor_si128(prod_hi, _mm_shuffle_epi8(t3_hi, data_1));
157        }
158
159        (prod_lo, prod_hi)
160    }
161
162    // {x_lo, x_hi} ^= {y_lo, y_hi} * log_m.
163    // Implementation of LEO_MULADD_128
164    #[inline(always)]
165    fn muladd_128(
166        mut x_lo: __m128i,
167        mut x_hi: __m128i,
168        y_lo: __m128i,
169        y_hi: __m128i,
170        lut: &Multiply128lutT,
171    ) -> (__m128i, __m128i) {
172        let (prod_lo, prod_hi) = Self::mul_128(y_lo, y_hi, lut);
173        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
174        unsafe {
175            x_lo = _mm_xor_si128(x_lo, prod_lo);
176            x_hi = _mm_xor_si128(x_hi, prod_hi);
177        }
178        (x_lo, x_hi)
179    }
180}
181
182// ======================================================================
183// Ssse3 - PRIVATE - FFT (fast Fourier transform)
184
185impl Ssse3 {
186    // Implementation of LEO_FFTB_128
187    #[inline(always)]
188    fn fftb_128(
189        &self,
190        x: &mut [u8; SHARD_CHUNK_BYTES],
191        y: &mut [u8; SHARD_CHUNK_BYTES],
192        log_m: GfElement,
193    ) {
194        let lut = &self.mul128[log_m as usize];
195        let x_ptr = x.as_mut_ptr().cast::<__m128i>();
196        let y_ptr = y.as_mut_ptr().cast::<__m128i>();
197        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
198        unsafe {
199            let mut x0_lo = _mm_loadu_si128(x_ptr);
200            let mut x1_lo = _mm_loadu_si128(x_ptr.add(1));
201            let mut x0_hi = _mm_loadu_si128(x_ptr.add(2));
202            let mut x1_hi = _mm_loadu_si128(x_ptr.add(3));
203
204            let mut y0_lo = _mm_loadu_si128(y_ptr);
205            let mut y1_lo = _mm_loadu_si128(y_ptr.add(1));
206            let mut y0_hi = _mm_loadu_si128(y_ptr.add(2));
207            let mut y1_hi = _mm_loadu_si128(y_ptr.add(3));
208
209            (x0_lo, x0_hi) = Self::muladd_128(x0_lo, x0_hi, y0_lo, y0_hi, lut);
210            (x1_lo, x1_hi) = Self::muladd_128(x1_lo, x1_hi, y1_lo, y1_hi, lut);
211
212            _mm_storeu_si128(x_ptr, x0_lo);
213            _mm_storeu_si128(x_ptr.add(1), x1_lo);
214            _mm_storeu_si128(x_ptr.add(2), x0_hi);
215            _mm_storeu_si128(x_ptr.add(3), x1_hi);
216
217            y0_lo = _mm_xor_si128(y0_lo, x0_lo);
218            y1_lo = _mm_xor_si128(y1_lo, x1_lo);
219            y0_hi = _mm_xor_si128(y0_hi, x0_hi);
220            y1_hi = _mm_xor_si128(y1_hi, x1_hi);
221
222            _mm_storeu_si128(y_ptr, y0_lo);
223            _mm_storeu_si128(y_ptr.add(1), y1_lo);
224            _mm_storeu_si128(y_ptr.add(2), y0_hi);
225            _mm_storeu_si128(y_ptr.add(3), y1_hi);
226        }
227    }
228
229    // Partial butterfly, caller must do `GF_MODULUS` check with `xor`.
230    #[inline(always)]
231    fn fft_butterfly_partial(
232        &self,
233        x: &mut [[u8; SHARD_CHUNK_BYTES]],
234        y: &mut [[u8; SHARD_CHUNK_BYTES]],
235        log_m: GfElement,
236    ) {
237        for (x_chunk, y_chunk) in zip(x.iter_mut(), y.iter_mut()) {
238            self.fftb_128(x_chunk, y_chunk, log_m);
239        }
240    }
241
242    #[inline(always)]
243    fn fft_butterfly_two_layers(
244        &self,
245        data: &mut ShardsRefMut<'_>,
246        pos: usize,
247        dist: usize,
248        log_m01: GfElement,
249        log_m23: GfElement,
250        log_m02: GfElement,
251    ) {
252        let (s0, s1, s2, s3) = data.dist4_mut(pos, dist);
253
254        // FIRST LAYER
255
256        if log_m02 == GF_MODULUS {
257            utils::xor(s2, s0);
258            utils::xor(s3, s1);
259        } else {
260            self.fft_butterfly_partial(s0, s2, log_m02);
261            self.fft_butterfly_partial(s1, s3, log_m02);
262        }
263
264        // SECOND LAYER
265
266        if log_m01 == GF_MODULUS {
267            utils::xor(s1, s0);
268        } else {
269            self.fft_butterfly_partial(s0, s1, log_m01);
270        }
271
272        if log_m23 == GF_MODULUS {
273            utils::xor(s3, s2);
274        } else {
275            self.fft_butterfly_partial(s2, s3, log_m23);
276        }
277    }
278
279    #[target_feature(enable = "ssse3")]
280    unsafe fn fft_private_ssse3(
281        &self,
282        data: &mut ShardsRefMut<'_>,
283        pos: usize,
284        size: usize,
285        truncated_size: usize,
286        skew_delta: usize,
287    ) {
288        self.fft_private(data, pos, size, truncated_size, skew_delta);
289    }
290
291    #[inline(always)]
292    fn fft_private(
293        &self,
294        data: &mut ShardsRefMut<'_>,
295        pos: usize,
296        size: usize,
297        truncated_size: usize,
298        skew_delta: usize,
299    ) {
300        // TWO LAYERS AT TIME
301
302        let mut dist4 = size;
303        let mut dist = size >> 2;
304        while dist != 0 {
305            let mut r = 0;
306            while r < truncated_size {
307                let base = r + dist + skew_delta - 1;
308
309                let log_m01 = self.skew[base];
310                let log_m02 = self.skew[base + dist];
311                let log_m23 = self.skew[base + dist * 2];
312
313                for i in r..r + dist {
314                    self.fft_butterfly_two_layers(data, pos + i, dist, log_m01, log_m23, log_m02);
315                }
316
317                r += dist4;
318            }
319            dist4 = dist;
320            dist >>= 2;
321        }
322
323        // FINAL ODD LAYER
324
325        if dist4 == 2 {
326            let mut r = 0;
327            while r < truncated_size {
328                let log_m = self.skew[r + skew_delta];
329
330                let (x, y) = data.dist2_mut(pos + r, 1);
331
332                if log_m == GF_MODULUS {
333                    utils::xor(y, x);
334                } else {
335                    self.fft_butterfly_partial(x, y, log_m);
336                }
337
338                r += 2;
339            }
340        }
341    }
342}
343
344// ======================================================================
345// Ssse3 - PRIVATE - IFFT (inverse fast Fourier transform)
346
347impl Ssse3 {
348    // Implementation of LEO_IFFTB_128
349    #[inline(always)]
350    fn ifftb_128(
351        &self,
352        x: &mut [u8; SHARD_CHUNK_BYTES],
353        y: &mut [u8; SHARD_CHUNK_BYTES],
354        log_m: GfElement,
355    ) {
356        let lut = &self.mul128[log_m as usize];
357        let x_ptr = x.as_mut_ptr().cast::<__m128i>();
358        let y_ptr = y.as_mut_ptr().cast::<__m128i>();
359
360        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
361        unsafe {
362            let mut x0_lo = _mm_loadu_si128(x_ptr);
363            let mut x1_lo = _mm_loadu_si128(x_ptr.add(1));
364            let mut x0_hi = _mm_loadu_si128(x_ptr.add(2));
365            let mut x1_hi = _mm_loadu_si128(x_ptr.add(3));
366
367            let mut y0_lo = _mm_loadu_si128(y_ptr);
368            let mut y1_lo = _mm_loadu_si128(y_ptr.add(1));
369            let mut y0_hi = _mm_loadu_si128(y_ptr.add(2));
370            let mut y1_hi = _mm_loadu_si128(y_ptr.add(3));
371
372            y0_lo = _mm_xor_si128(y0_lo, x0_lo);
373            y1_lo = _mm_xor_si128(y1_lo, x1_lo);
374            y0_hi = _mm_xor_si128(y0_hi, x0_hi);
375            y1_hi = _mm_xor_si128(y1_hi, x1_hi);
376
377            _mm_storeu_si128(y_ptr, y0_lo);
378            _mm_storeu_si128(y_ptr.add(1), y1_lo);
379            _mm_storeu_si128(y_ptr.add(2), y0_hi);
380            _mm_storeu_si128(y_ptr.add(3), y1_hi);
381
382            (x0_lo, x0_hi) = Self::muladd_128(x0_lo, x0_hi, y0_lo, y0_hi, lut);
383            (x1_lo, x1_hi) = Self::muladd_128(x1_lo, x1_hi, y1_lo, y1_hi, lut);
384
385            _mm_storeu_si128(x_ptr, x0_lo);
386            _mm_storeu_si128(x_ptr.add(1), x1_lo);
387            _mm_storeu_si128(x_ptr.add(2), x0_hi);
388            _mm_storeu_si128(x_ptr.add(3), x1_hi);
389        }
390    }
391
392    #[inline(always)]
393    fn ifft_butterfly_partial(
394        &self,
395        x: &mut [[u8; SHARD_CHUNK_BYTES]],
396        y: &mut [[u8; SHARD_CHUNK_BYTES]],
397        log_m: GfElement,
398    ) {
399        for (x_chunk, y_chunk) in zip(x.iter_mut(), y.iter_mut()) {
400            self.ifftb_128(x_chunk, y_chunk, log_m);
401        }
402    }
403
404    #[inline(always)]
405    fn ifft_butterfly_two_layers(
406        &self,
407        data: &mut ShardsRefMut<'_>,
408        pos: usize,
409        dist: usize,
410        log_m01: GfElement,
411        log_m23: GfElement,
412        log_m02: GfElement,
413    ) {
414        let (s0, s1, s2, s3) = data.dist4_mut(pos, dist);
415
416        // FIRST LAYER
417
418        if log_m01 == GF_MODULUS {
419            utils::xor(s1, s0);
420        } else {
421            self.ifft_butterfly_partial(s0, s1, log_m01);
422        }
423
424        if log_m23 == GF_MODULUS {
425            utils::xor(s3, s2);
426        } else {
427            self.ifft_butterfly_partial(s2, s3, log_m23);
428        }
429
430        // SECOND LAYER
431
432        if log_m02 == GF_MODULUS {
433            utils::xor(s2, s0);
434            utils::xor(s3, s1);
435        } else {
436            self.ifft_butterfly_partial(s0, s2, log_m02);
437            self.ifft_butterfly_partial(s1, s3, log_m02);
438        }
439    }
440
441    #[target_feature(enable = "ssse3")]
442    unsafe fn ifft_private_ssse3(
443        &self,
444        data: &mut ShardsRefMut<'_>,
445        pos: usize,
446        size: usize,
447        truncated_size: usize,
448        skew_delta: usize,
449    ) {
450        self.ifft_private(data, pos, size, truncated_size, skew_delta);
451    }
452
453    #[inline(always)]
454    fn ifft_private(
455        &self,
456        data: &mut ShardsRefMut<'_>,
457        pos: usize,
458        size: usize,
459        truncated_size: usize,
460        skew_delta: usize,
461    ) {
462        // TWO LAYERS AT TIME
463
464        let mut dist = 1;
465        let mut dist4 = 4;
466        while dist4 <= size {
467            let mut r = 0;
468            while r < truncated_size {
469                let base = r + dist + skew_delta - 1;
470
471                let log_m01 = self.skew[base];
472                let log_m02 = self.skew[base + dist];
473                let log_m23 = self.skew[base + dist * 2];
474
475                for i in r..r + dist {
476                    self.ifft_butterfly_two_layers(data, pos + i, dist, log_m01, log_m23, log_m02);
477                }
478
479                r += dist4;
480            }
481            dist = dist4;
482            dist4 <<= 2;
483        }
484
485        // FINAL ODD LAYER
486
487        if dist < size {
488            let log_m = self.skew[dist + skew_delta - 1];
489            if log_m == GF_MODULUS {
490                utils::xor_within(data, pos + dist, pos, dist);
491            } else {
492                let (mut a, mut b) = data.split_at_mut(pos + dist);
493                for i in 0..dist {
494                    self.ifft_butterfly_partial(
495                        &mut a[pos + i], // data[pos + i]
496                        &mut b[i],       // data[pos + i + dist]
497                        log_m,
498                    );
499                }
500            }
501        }
502    }
503}
504
505// ======================================================================
506// Ssse3 - PRIVATE - Evaluate polynomial
507
508impl Ssse3 {
509    #[target_feature(enable = "ssse3")]
510    unsafe fn eval_poly_ssse3(erasures: &mut [GfElement; GF_ORDER], truncated_size: usize) {
511        utils::eval_poly(erasures, truncated_size);
512    }
513}
514
515// ======================================================================
516// TESTS
517
518// Engines are tested indirectly via roundtrip tests of HighRate and LowRate.