poulpy/poulpy-backend/src/cpu_fft64_avx/reim/fft_avx2_fma.rs
poulpy-backend/src/cpu_fft64_avx/reim/fft_avx2_fma.rsRaw
1use std::arch::x86_64::{
2 __m128d, __m256d, _mm_load_pd, _mm256_add_pd, _mm256_fmadd_pd, _mm256_fmsub_pd, _mm256_loadu_pd, _mm256_mul_pd,
3 _mm256_permute2f128_pd, _mm256_set_m128d, _mm256_storeu_pd, _mm256_sub_pd, _mm256_unpackhi_pd, _mm256_unpacklo_pd,
4};
5
6use crate::cpu_fft64_avx::reim::{as_arr, as_arr_mut};
7
8#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
9#[target_feature(enable = "avx2,fma")]
10pub(crate) fn fft_avx2_fma(m: usize, omg: &[f64], data: &mut [f64]) {
11 if m < 16 {
12 use poulpy_hal::reference::fft64::reim::fft_ref;
13
14 fft_ref(m, omg, data);
15 return;
16 }
17
18 assert!(data.len() == 2 * m);
19 let (re, im) = data.split_at_mut(m);
20
21 if m == 16 {
22 fft16_avx2_fma(
23 as_arr_mut::<16, f64>(re),
24 as_arr_mut::<16, f64>(im),
25 as_arr::<16, f64>(omg),
26 )
27 } else if m <= 2048 {
28 fft_bfs_16_avx2_fma(m, re, im, omg, 0);
29 } else {
30 fft_rec_16_avx2_fma(m, re, im, omg, 0);
31 }
32}
33
34unsafe extern "sysv64" {
35 unsafe fn fft16_avx2_fma_asm(re: *mut f64, im: *mut f64, omg: *const f64);
36}
37
38#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
39#[target_feature(enable = "avx2,fma")]
40fn fft16_avx2_fma(re: &mut [f64; 16], im: &mut [f64; 16], omg: &[f64; 16]) {
41 unsafe {
42 fft16_avx2_fma_asm(re.as_mut_ptr(), im.as_mut_ptr(), omg.as_ptr());
43 }
44}
45
46#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
47#[target_feature(enable = "avx2,fma")]
48fn fft_rec_16_avx2_fma(m: usize, re: &mut [f64], im: &mut [f64], omg: &[f64], mut pos: usize) -> usize {
49 if m <= 2048 {
50 return fft_bfs_16_avx2_fma(m, re, im, omg, pos);
51 };
52
53 let h: usize = m >> 1;
54 twiddle_fft_avx2_fma(h, re, im, *as_arr::<2, f64>(&omg[pos..]));
55 pos += 2;
56 pos = fft_rec_16_avx2_fma(h, re, im, omg, pos);
57 pos = fft_rec_16_avx2_fma(h, &mut re[h..], &mut im[h..], omg, pos);
58 pos
59}
60
61#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
62#[target_feature(enable = "avx2,fma")]
63fn fft_bfs_16_avx2_fma(m: usize, re: &mut [f64], im: &mut [f64], omg: &[f64], mut pos: usize) -> usize {
64 let log_m: usize = (usize::BITS - (m - 1).leading_zeros()) as usize;
65 let mut mm: usize = m;
66
67 if !log_m.is_multiple_of(2) {
68 let h: usize = mm >> 1;
69 twiddle_fft_avx2_fma(h, re, im, *as_arr::<2, f64>(&omg[pos..]));
70 pos += 2;
71 mm = h
72 }
73
74 while mm > 16 {
75 let h: usize = mm >> 2;
76 for off in (0..m).step_by(mm) {
77 bitwiddle_fft_avx2_fma(
78 h,
79 &mut re[off..],
80 &mut im[off..],
81 as_arr::<4, f64>(&omg[pos..]),
82 );
83
84 pos += 4;
85 }
86 mm = h
87 }
88
89 for off in (0..m).step_by(16) {
90 fft16_avx2_fma(
91 as_arr_mut::<16, f64>(&mut re[off..]),
92 as_arr_mut::<16, f64>(&mut im[off..]),
93 as_arr::<16, f64>(&omg[pos..]),
94 );
95
96 pos += 16;
97 }
98
99 pos
100}
101
102#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
103#[target_feature(enable = "avx2,fma")]
104fn twiddle_fft_avx2_fma(h: usize, re: &mut [f64], im: &mut [f64], omg: [f64; 2]) {
105 unsafe {
106 let omx: __m128d = _mm_load_pd(omg.as_ptr());
107 let omra: __m256d = _mm256_set_m128d(omx, omx);
108 let omi: __m256d = _mm256_unpackhi_pd(omra, omra);
109 let omr: __m256d = _mm256_unpacklo_pd(omra, omra);
110 let mut r0: *mut f64 = re.as_mut_ptr();
111 let mut r1: *mut f64 = re.as_mut_ptr().add(h);
112 let mut i0: *mut f64 = im.as_mut_ptr();
113 let mut i1: *mut f64 = im.as_mut_ptr().add(h);
114
115 for _ in (0..h).step_by(4) {
116 let mut ur0: __m256d = _mm256_loadu_pd(r0);
117 let mut ur1: __m256d = _mm256_loadu_pd(r1);
118 let mut ui0: __m256d = _mm256_loadu_pd(i0);
119 let mut ui1: __m256d = _mm256_loadu_pd(i1);
120 let mut tra: __m256d = _mm256_mul_pd(omi, ui1);
121 let mut tia: __m256d = _mm256_mul_pd(omi, ur1);
122
123 tra = _mm256_fmsub_pd(omr, ur1, tra);
124 tia = _mm256_fmadd_pd(omr, ui1, tia);
125 ur1 = _mm256_sub_pd(ur0, tra);
126 ui1 = _mm256_sub_pd(ui0, tia);
127 ur0 = _mm256_add_pd(ur0, tra);
128 ui0 = _mm256_add_pd(ui0, tia);
129
130 _mm256_storeu_pd(r0, ur0);
131 _mm256_storeu_pd(r1, ur1);
132 _mm256_storeu_pd(i0, ui0);
133 _mm256_storeu_pd(i1, ui1);
134
135 r0 = r0.add(4);
136 r1 = r1.add(4);
137 i0 = i0.add(4);
138 i1 = i1.add(4);
139 }
140 }
141}
142
143#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
144#[target_feature(enable = "avx2,fma")]
145fn bitwiddle_fft_avx2_fma(h: usize, re: &mut [f64], im: &mut [f64], omg: &[f64; 4]) {
146 unsafe {
147 let mut r0: *mut f64 = re.as_mut_ptr();
148 let mut r1: *mut f64 = re.as_mut_ptr().add(h);
149 let mut r2: *mut f64 = re.as_mut_ptr().add(2 * h);
150 let mut r3: *mut f64 = re.as_mut_ptr().add(3 * h);
151 let mut i0: *mut f64 = im.as_mut_ptr();
152 let mut i1: *mut f64 = im.as_mut_ptr().add(h);
153 let mut i2: *mut f64 = im.as_mut_ptr().add(2 * h);
154 let mut i3: *mut f64 = im.as_mut_ptr().add(3 * h);
155 let om0: __m256d = _mm256_loadu_pd(omg.as_ptr());
156 let omb: __m256d = _mm256_permute2f128_pd(om0, om0, 0x11);
157 let oma: __m256d = _mm256_permute2f128_pd(om0, om0, 0x00);
158 let omai: __m256d = _mm256_unpackhi_pd(oma, oma);
159 let omar: __m256d = _mm256_unpacklo_pd(oma, oma);
160 let ombi: __m256d = _mm256_unpackhi_pd(omb, omb);
161 let ombr: __m256d = _mm256_unpacklo_pd(omb, omb);
162 for _ in (0..h).step_by(4) {
163 let mut ur0: __m256d = _mm256_loadu_pd(r0);
164 let mut ur1: __m256d = _mm256_loadu_pd(r1);
165 let mut ur2: __m256d = _mm256_loadu_pd(r2);
166 let mut ur3: __m256d = _mm256_loadu_pd(r3);
167 let mut ui0: __m256d = _mm256_loadu_pd(i0);
168 let mut ui1: __m256d = _mm256_loadu_pd(i1);
169 let mut ui2: __m256d = _mm256_loadu_pd(i2);
170 let mut ui3: __m256d = _mm256_loadu_pd(i3);
171
172 let mut tra: __m256d = _mm256_mul_pd(omai, ui2);
173 let mut trb: __m256d = _mm256_mul_pd(omai, ui3);
174 let mut tia: __m256d = _mm256_mul_pd(omai, ur2);
175 let mut tib: __m256d = _mm256_mul_pd(omai, ur3);
176 tra = _mm256_fmsub_pd(omar, ur2, tra);
177 trb = _mm256_fmsub_pd(omar, ur3, trb);
178 tia = _mm256_fmadd_pd(omar, ui2, tia);
179 tib = _mm256_fmadd_pd(omar, ui3, tib);
180 ur2 = _mm256_sub_pd(ur0, tra);
181 ur3 = _mm256_sub_pd(ur1, trb);
182 ui2 = _mm256_sub_pd(ui0, tia);
183 ui3 = _mm256_sub_pd(ui1, tib);
184 ur0 = _mm256_add_pd(ur0, tra);
185 ur1 = _mm256_add_pd(ur1, trb);
186 ui0 = _mm256_add_pd(ui0, tia);
187 ui1 = _mm256_add_pd(ui1, tib);
188
189 tra = _mm256_mul_pd(ombi, ui1);
190 trb = _mm256_mul_pd(ombr, ui3);
191 tia = _mm256_mul_pd(ombi, ur1);
192 tib = _mm256_mul_pd(ombr, ur3);
193 tra = _mm256_fmsub_pd(ombr, ur1, tra);
194 trb = _mm256_fmadd_pd(ombi, ur3, trb);
195 tia = _mm256_fmadd_pd(ombr, ui1, tia);
196 tib = _mm256_fmsub_pd(ombi, ui3, tib);
197 ur1 = _mm256_sub_pd(ur0, tra);
198 ur3 = _mm256_add_pd(ur2, trb);
199 ui1 = _mm256_sub_pd(ui0, tia);
200 ui3 = _mm256_add_pd(ui2, tib);
201 ur0 = _mm256_add_pd(ur0, tra);
202 ur2 = _mm256_sub_pd(ur2, trb);
203 ui0 = _mm256_add_pd(ui0, tia);
204 ui2 = _mm256_sub_pd(ui2, tib);
205
206 _mm256_storeu_pd(r0, ur0);
207 _mm256_storeu_pd(r1, ur1);
208 _mm256_storeu_pd(r2, ur2);
209 _mm256_storeu_pd(r3, ur3);
210 _mm256_storeu_pd(i0, ui0);
211 _mm256_storeu_pd(i1, ui1);
212 _mm256_storeu_pd(i2, ui2);
213 _mm256_storeu_pd(i3, ui3);
214
215 r0 = r0.add(4);
216 r1 = r1.add(4);
217 r2 = r2.add(4);
218 r3 = r3.add(4);
219 i0 = i0.add(4);
220 i1 = i1.add(4);
221 i2 = i2.add(4);
222 i3 = i3.add(4);
223 }
224 }
225}
226
227#[test]
228fn test_fft_avx2_fma() {
229 use super::*;
230
231 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
232 #[target_feature(enable = "avx2,fma")]
233 fn internal(log_m: usize) {
234 use poulpy_hal::reference::fft64::reim::ReimFFTRef;
235
236 let m = 1 << log_m;
237
238 let table: ReimFFTTable<f64> = ReimFFTTable::<f64>::new(m);
239
240 let mut values_0: Vec<f64> = vec![0f64; m << 1];
241 let scale: f64 = 1.0f64 / m as f64;
242 values_0
243 .iter_mut()
244 .enumerate()
245 .for_each(|(i, x)| *x = (i + 1) as f64 * scale);
246
247 let mut values_1: Vec<f64> = vec![0f64; m << 1];
248 values_1
249 .iter_mut()
250 .zip(values_0.iter())
251 .for_each(|(y, x)| *y = *x);
252
253 ReimFFTAvx::reim_dft_execute(&table, &mut values_0);
254 ReimFFTRef::reim_dft_execute(&table, &mut values_1);
255
256 let max_diff: f64 = 1.0 / ((1u64 << (53 - log_m - 1)) as f64);
257
258 for i in 0..m * 2 {
259 let diff: f64 = (values_0[i] - values_1[i]).abs();
260 assert!(
261 diff <= max_diff,
262 "{} -> {}-{} = {}",
263 i,
264 values_0[i],
265 values_1[i],
266 diff
267 )
268 }
269 }
270
271 if std::is_x86_feature_detected!("avx2") {
272 for log_m in 0..16 {
273 unsafe { internal(log_m) }
274 }
275 } else {
276 eprintln!("skipping: CPU lacks avx2");
277 }
278}