Skip to main content

commonware_cryptography/reed_solomon/engine/
engine_avx2.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// Avx2 - PUBLIC
13
14/// Optimized [`Engine`] using AVX2 instructions.
15///
16/// [`Avx2`] is an optimized engine that follows the same algorithm as
17/// [`NoSimd`] but takes advantage of the x86 AVX2 SIMD instructions.
18///
19/// [`NoSimd`]: crate::reed_solomon::engine::NoSimd
20#[derive(Clone, Copy)]
21pub struct Avx2 {
22    mul128: &'static Mul128,
23    skew: &'static Skew,
24}
25
26impl Avx2 {
27    /// Creates new [`Avx2`], 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_avx2_for_engine, "avx2");
36        assert!(has_avx2_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 Avx2 {
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_avx2(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_avx2(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_avx2(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_avx2(erasures, truncated_size) }
84    }
85}
86
87// ======================================================================
88// Avx2 - IMPL Default
89
90impl Default for Avx2 {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96// ======================================================================
97// Avx2 - PRIVATE
98
99#[derive(Copy, Clone)]
100struct LutAvx2 {
101    t0_lo: __m256i,
102    t1_lo: __m256i,
103    t2_lo: __m256i,
104    t3_lo: __m256i,
105    t0_hi: __m256i,
106    t1_hi: __m256i,
107    t2_hi: __m256i,
108    t3_hi: __m256i,
109}
110
111impl From<&Multiply128lutT> for LutAvx2 {
112    #[inline(always)]
113    fn from(lut: &Multiply128lutT) -> Self {
114        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
115        unsafe {
116            Self {
117                t0_lo: _mm256_broadcastsi128_si256(_mm_loadu_si128(
118                    core::ptr::from_ref::<u128>(&lut.lo[0]).cast::<__m128i>(),
119                )),
120                t1_lo: _mm256_broadcastsi128_si256(_mm_loadu_si128(
121                    core::ptr::from_ref::<u128>(&lut.lo[1]).cast::<__m128i>(),
122                )),
123                t2_lo: _mm256_broadcastsi128_si256(_mm_loadu_si128(
124                    core::ptr::from_ref::<u128>(&lut.lo[2]).cast::<__m128i>(),
125                )),
126                t3_lo: _mm256_broadcastsi128_si256(_mm_loadu_si128(
127                    core::ptr::from_ref::<u128>(&lut.lo[3]).cast::<__m128i>(),
128                )),
129                t0_hi: _mm256_broadcastsi128_si256(_mm_loadu_si128(
130                    core::ptr::from_ref::<u128>(&lut.hi[0]).cast::<__m128i>(),
131                )),
132                t1_hi: _mm256_broadcastsi128_si256(_mm_loadu_si128(
133                    core::ptr::from_ref::<u128>(&lut.hi[1]).cast::<__m128i>(),
134                )),
135                t2_hi: _mm256_broadcastsi128_si256(_mm_loadu_si128(
136                    core::ptr::from_ref::<u128>(&lut.hi[2]).cast::<__m128i>(),
137                )),
138                t3_hi: _mm256_broadcastsi128_si256(_mm_loadu_si128(
139                    core::ptr::from_ref::<u128>(&lut.hi[3]).cast::<__m128i>(),
140                )),
141            }
142        }
143    }
144}
145
146impl Avx2 {
147    #[target_feature(enable = "avx2")]
148    unsafe fn mul_avx2(&self, x: &mut [[u8; SHARD_CHUNK_BYTES]], log_m: GfElement) {
149        let lut = &self.mul128[log_m as usize];
150        let lut_avx2 = LutAvx2::from(lut);
151
152        for chunk in x.iter_mut() {
153            let x_ptr = chunk.as_mut_ptr().cast::<__m256i>();
154            // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
155            unsafe {
156                let x_lo = _mm256_loadu_si256(x_ptr);
157                let x_hi = _mm256_loadu_si256(x_ptr.add(1));
158                let (prod_lo, prod_hi) = Self::mul_256(x_lo, x_hi, lut_avx2);
159                _mm256_storeu_si256(x_ptr, prod_lo);
160                _mm256_storeu_si256(x_ptr.add(1), prod_hi);
161            }
162        }
163    }
164
165    // Implementation of LEO_MUL_256
166    #[inline(always)]
167    fn mul_256(value_lo: __m256i, value_hi: __m256i, lut_avx2: LutAvx2) -> (__m256i, __m256i) {
168        let mut prod_lo: __m256i;
169        let mut prod_hi: __m256i;
170
171        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
172        unsafe {
173            let clr_mask = _mm256_set1_epi8(0x0f);
174
175            let data_0 = _mm256_and_si256(value_lo, clr_mask);
176            prod_lo = _mm256_shuffle_epi8(lut_avx2.t0_lo, data_0);
177            prod_hi = _mm256_shuffle_epi8(lut_avx2.t0_hi, data_0);
178
179            let data_1 = _mm256_and_si256(_mm256_srli_epi64(value_lo, 4), clr_mask);
180            prod_lo = _mm256_xor_si256(prod_lo, _mm256_shuffle_epi8(lut_avx2.t1_lo, data_1));
181            prod_hi = _mm256_xor_si256(prod_hi, _mm256_shuffle_epi8(lut_avx2.t1_hi, data_1));
182
183            let data_0 = _mm256_and_si256(value_hi, clr_mask);
184            prod_lo = _mm256_xor_si256(prod_lo, _mm256_shuffle_epi8(lut_avx2.t2_lo, data_0));
185            prod_hi = _mm256_xor_si256(prod_hi, _mm256_shuffle_epi8(lut_avx2.t2_hi, data_0));
186
187            let data_1 = _mm256_and_si256(_mm256_srli_epi64(value_hi, 4), clr_mask);
188            prod_lo = _mm256_xor_si256(prod_lo, _mm256_shuffle_epi8(lut_avx2.t3_lo, data_1));
189            prod_hi = _mm256_xor_si256(prod_hi, _mm256_shuffle_epi8(lut_avx2.t3_hi, data_1));
190        }
191
192        (prod_lo, prod_hi)
193    }
194
195    // {x_lo, x_hi} ^= {y_lo, y_hi} * log_m.
196    // Implementation of LEO_MULADD_256
197    #[inline(always)]
198    fn muladd_256(
199        mut x_lo: __m256i,
200        mut x_hi: __m256i,
201        y_lo: __m256i,
202        y_hi: __m256i,
203        lut_avx2: LutAvx2,
204    ) -> (__m256i, __m256i) {
205        let (prod_lo, prod_hi) = Self::mul_256(y_lo, y_hi, lut_avx2);
206        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
207        unsafe {
208            x_lo = _mm256_xor_si256(x_lo, prod_lo);
209            x_hi = _mm256_xor_si256(x_hi, prod_hi);
210        }
211        (x_lo, x_hi)
212    }
213}
214
215// ======================================================================
216// Avx2 - PRIVATE - FFT (fast Fourier transform)
217
218impl Avx2 {
219    // Implementation of LEO_FFTB_256
220    #[inline(always)]
221    fn fftb_256(
222        x: &mut [u8; SHARD_CHUNK_BYTES],
223        y: &mut [u8; SHARD_CHUNK_BYTES],
224        lut_avx2: LutAvx2,
225    ) {
226        let x_ptr = x.as_mut_ptr().cast::<__m256i>();
227        let y_ptr = y.as_mut_ptr().cast::<__m256i>();
228
229        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
230        unsafe {
231            let mut x_lo = _mm256_loadu_si256(x_ptr);
232            let mut x_hi = _mm256_loadu_si256(x_ptr.add(1));
233
234            let mut y_lo = _mm256_loadu_si256(y_ptr);
235            let mut y_hi = _mm256_loadu_si256(y_ptr.add(1));
236
237            (x_lo, x_hi) = Self::muladd_256(x_lo, x_hi, y_lo, y_hi, lut_avx2);
238
239            _mm256_storeu_si256(x_ptr, x_lo);
240            _mm256_storeu_si256(x_ptr.add(1), x_hi);
241
242            y_lo = _mm256_xor_si256(y_lo, x_lo);
243            y_hi = _mm256_xor_si256(y_hi, x_hi);
244
245            _mm256_storeu_si256(y_ptr, y_lo);
246            _mm256_storeu_si256(y_ptr.add(1), y_hi);
247        }
248    }
249
250    // Partial butterfly, caller must do `GF_MODULUS` check with `xor`.
251    #[inline(always)]
252    fn fft_butterfly_partial(
253        &self,
254        x: &mut [[u8; SHARD_CHUNK_BYTES]],
255        y: &mut [[u8; SHARD_CHUNK_BYTES]],
256        log_m: GfElement,
257    ) {
258        let lut = &self.mul128[log_m as usize];
259        let lut_avx2 = LutAvx2::from(lut);
260
261        for (x_chunk, y_chunk) in zip(x.iter_mut(), y.iter_mut()) {
262            Self::fftb_256(x_chunk, y_chunk, lut_avx2);
263        }
264    }
265
266    #[inline(always)]
267    fn fft_butterfly_two_layers(
268        &self,
269        data: &mut ShardsRefMut<'_>,
270        pos: usize,
271        dist: usize,
272        log_m01: GfElement,
273        log_m23: GfElement,
274        log_m02: GfElement,
275    ) {
276        let (s0, s1, s2, s3) = data.dist4_mut(pos, dist);
277
278        // FIRST LAYER
279
280        if log_m02 == GF_MODULUS {
281            utils::xor(s2, s0);
282            utils::xor(s3, s1);
283        } else {
284            self.fft_butterfly_partial(s0, s2, log_m02);
285            self.fft_butterfly_partial(s1, s3, log_m02);
286        }
287
288        // SECOND LAYER
289
290        if log_m01 == GF_MODULUS {
291            utils::xor(s1, s0);
292        } else {
293            self.fft_butterfly_partial(s0, s1, log_m01);
294        }
295
296        if log_m23 == GF_MODULUS {
297            utils::xor(s3, s2);
298        } else {
299            self.fft_butterfly_partial(s2, s3, log_m23);
300        }
301    }
302
303    #[target_feature(enable = "avx2")]
304    unsafe fn fft_private_avx2(
305        &self,
306        data: &mut ShardsRefMut<'_>,
307        pos: usize,
308        size: usize,
309        truncated_size: usize,
310        skew_delta: usize,
311    ) {
312        self.fft_private(data, pos, size, truncated_size, skew_delta);
313    }
314
315    #[inline(always)]
316    fn fft_private(
317        &self,
318        data: &mut ShardsRefMut<'_>,
319        pos: usize,
320        size: usize,
321        truncated_size: usize,
322        skew_delta: usize,
323    ) {
324        // TWO LAYERS AT TIME
325
326        let mut dist4 = size;
327        let mut dist = size >> 2;
328        while dist != 0 {
329            let mut r = 0;
330            while r < truncated_size {
331                let base = r + dist + skew_delta - 1;
332
333                let log_m01 = self.skew[base];
334                let log_m02 = self.skew[base + dist];
335                let log_m23 = self.skew[base + dist * 2];
336
337                for i in r..r + dist {
338                    self.fft_butterfly_two_layers(data, pos + i, dist, log_m01, log_m23, log_m02);
339                }
340
341                r += dist4;
342            }
343            dist4 = dist;
344            dist >>= 2;
345        }
346
347        // FINAL ODD LAYER
348
349        if dist4 == 2 {
350            let mut r = 0;
351            while r < truncated_size {
352                let log_m = self.skew[r + skew_delta];
353
354                let (x, y) = data.dist2_mut(pos + r, 1);
355
356                if log_m == GF_MODULUS {
357                    utils::xor(y, x);
358                } else {
359                    self.fft_butterfly_partial(x, y, log_m);
360                }
361
362                r += 2;
363            }
364        }
365    }
366}
367
368// ======================================================================
369// Avx2 - PRIVATE - IFFT (inverse fast Fourier transform)
370
371impl Avx2 {
372    // Implementation of LEO_IFFTB_256
373    #[inline(always)]
374    fn ifftb_256(
375        x: &mut [u8; SHARD_CHUNK_BYTES],
376        y: &mut [u8; SHARD_CHUNK_BYTES],
377        lut_avx2: LutAvx2,
378    ) {
379        let x_ptr = x.as_mut_ptr().cast::<__m256i>();
380        let y_ptr = y.as_mut_ptr().cast::<__m256i>();
381
382        // SAFETY: Constructors and runtime dispatch ensure the SIMD feature is available; offsets stay within fixed-size shard buffers.
383        unsafe {
384            let mut x_lo = _mm256_loadu_si256(x_ptr);
385            let mut x_hi = _mm256_loadu_si256(x_ptr.add(1));
386
387            let mut y_lo = _mm256_loadu_si256(y_ptr);
388            let mut y_hi = _mm256_loadu_si256(y_ptr.add(1));
389
390            y_lo = _mm256_xor_si256(y_lo, x_lo);
391            y_hi = _mm256_xor_si256(y_hi, x_hi);
392
393            _mm256_storeu_si256(y_ptr, y_lo);
394            _mm256_storeu_si256(y_ptr.add(1), y_hi);
395
396            (x_lo, x_hi) = Self::muladd_256(x_lo, x_hi, y_lo, y_hi, lut_avx2);
397
398            _mm256_storeu_si256(x_ptr, x_lo);
399            _mm256_storeu_si256(x_ptr.add(1), x_hi);
400        }
401    }
402
403    #[inline(always)]
404    fn ifft_butterfly_partial(
405        &self,
406        x: &mut [[u8; SHARD_CHUNK_BYTES]],
407        y: &mut [[u8; SHARD_CHUNK_BYTES]],
408        log_m: GfElement,
409    ) {
410        let lut = &self.mul128[log_m as usize];
411        let lut_avx2 = LutAvx2::from(lut);
412
413        for (x_chunk, y_chunk) in zip(x.iter_mut(), y.iter_mut()) {
414            Self::ifftb_256(x_chunk, y_chunk, lut_avx2);
415        }
416    }
417
418    #[inline(always)]
419    fn ifft_butterfly_two_layers(
420        &self,
421        data: &mut ShardsRefMut<'_>,
422        pos: usize,
423        dist: usize,
424        log_m01: GfElement,
425        log_m23: GfElement,
426        log_m02: GfElement,
427    ) {
428        let (s0, s1, s2, s3) = data.dist4_mut(pos, dist);
429
430        // FIRST LAYER
431
432        if log_m01 == GF_MODULUS {
433            utils::xor(s1, s0);
434        } else {
435            self.ifft_butterfly_partial(s0, s1, log_m01);
436        }
437
438        if log_m23 == GF_MODULUS {
439            utils::xor(s3, s2);
440        } else {
441            self.ifft_butterfly_partial(s2, s3, log_m23);
442        }
443
444        // SECOND LAYER
445
446        if log_m02 == GF_MODULUS {
447            utils::xor(s2, s0);
448            utils::xor(s3, s1);
449        } else {
450            self.ifft_butterfly_partial(s0, s2, log_m02);
451            self.ifft_butterfly_partial(s1, s3, log_m02);
452        }
453    }
454
455    #[target_feature(enable = "avx2")]
456    unsafe fn ifft_private_avx2(
457        &self,
458        data: &mut ShardsRefMut<'_>,
459        pos: usize,
460        size: usize,
461        truncated_size: usize,
462        skew_delta: usize,
463    ) {
464        self.ifft_private(data, pos, size, truncated_size, skew_delta);
465    }
466
467    #[inline(always)]
468    fn ifft_private(
469        &self,
470        data: &mut ShardsRefMut<'_>,
471        pos: usize,
472        size: usize,
473        truncated_size: usize,
474        skew_delta: usize,
475    ) {
476        // TWO LAYERS AT TIME
477
478        let mut dist = 1;
479        let mut dist4 = 4;
480        while dist4 <= size {
481            let mut r = 0;
482            while r < truncated_size {
483                let base = r + dist + skew_delta - 1;
484
485                let log_m01 = self.skew[base];
486                let log_m02 = self.skew[base + dist];
487                let log_m23 = self.skew[base + dist * 2];
488
489                for i in r..r + dist {
490                    self.ifft_butterfly_two_layers(data, pos + i, dist, log_m01, log_m23, log_m02);
491                }
492
493                r += dist4;
494            }
495            dist = dist4;
496            dist4 <<= 2;
497        }
498
499        // FINAL ODD LAYER
500
501        if dist < size {
502            let log_m = self.skew[dist + skew_delta - 1];
503            if log_m == GF_MODULUS {
504                utils::xor_within(data, pos + dist, pos, dist);
505            } else {
506                let (mut a, mut b) = data.split_at_mut(pos + dist);
507                for i in 0..dist {
508                    self.ifft_butterfly_partial(
509                        &mut a[pos + i], // data[pos + i]
510                        &mut b[i],       // data[pos + i + dist]
511                        log_m,
512                    );
513                }
514            }
515        }
516    }
517}
518
519// ======================================================================
520// Avx2 - PRIVATE - Evaluate polynomial
521
522impl Avx2 {
523    #[target_feature(enable = "avx2")]
524    unsafe fn eval_poly_avx2(erasures: &mut [GfElement; GF_ORDER], truncated_size: usize) {
525        utils::eval_poly(erasures, truncated_size);
526    }
527}
528
529// ======================================================================
530// TESTS
531
532// Engines are tested indirectly via roundtrip tests of HighRate and LowRate.