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