1use std::sync::OnceLock;
10
11pub trait SimdKernel: Send + Sync {
21 fn batch_add(&self, a: &[f64], b: &[f64], out: &mut [f64]);
23 fn batch_sub(&self, a: &[f64], b: &[f64], out: &mut [f64]);
25 fn batch_mul(&self, a: &[f64], b: &[f64], out: &mut [f64]);
27 fn batch_div(&self, a: &[f64], b: &[f64], out: &mut [f64]);
29 fn batch_fma(&self, a: &[f64], b: &[f64], c: &[f64], out: &mut [f64]);
31 fn batch_sqrt(&self, a: &[f64], out: &mut [f64]);
33 fn batch_neg(&self, a: &[f64], out: &mut [f64]);
35 fn batch_abs(&self, a: &[f64], out: &mut [f64]);
37 fn name(&self) -> &'static str;
39}
40
41pub struct ScalarKernel;
47
48impl SimdKernel for ScalarKernel {
49 fn batch_add(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
50 for i in 0..out.len() {
51 out[i] = a[i] + b[i];
52 }
53 }
54
55 fn batch_sub(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
56 for i in 0..out.len() {
57 out[i] = a[i] - b[i];
58 }
59 }
60
61 fn batch_mul(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
62 for i in 0..out.len() {
63 out[i] = a[i] * b[i];
64 }
65 }
66
67 fn batch_div(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
68 for i in 0..out.len() {
69 out[i] = a[i] / b[i];
70 }
71 }
72
73 fn batch_fma(&self, a: &[f64], b: &[f64], c: &[f64], out: &mut [f64]) {
74 for i in 0..out.len() {
75 out[i] = a[i].mul_add(b[i], c[i]);
76 }
77 }
78
79 fn batch_sqrt(&self, a: &[f64], out: &mut [f64]) {
80 for i in 0..out.len() {
81 out[i] = a[i].sqrt();
82 }
83 }
84
85 fn batch_neg(&self, a: &[f64], out: &mut [f64]) {
86 for i in 0..out.len() {
87 out[i] = -a[i];
88 }
89 }
90
91 fn batch_abs(&self, a: &[f64], out: &mut [f64]) {
92 for i in 0..out.len() {
93 out[i] = a[i].abs();
94 }
95 }
96
97 fn name(&self) -> &'static str {
98 "scalar"
99 }
100}
101
102#[cfg(target_arch = "x86_64")]
111pub struct Avx2Kernel;
112
113#[cfg(target_arch = "x86_64")]
114impl SimdKernel for Avx2Kernel {
115 fn batch_add(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
116 use crate::asm_presets::add_f64x4;
117 const LANES: usize = 4;
118 let n = out.len();
119 let mut i = 0;
120 while i + LANES <= n {
121 add_f64x4::apply(&a[i..i + LANES], &b[i..i + LANES], &mut out[i..i + LANES]);
122 i += LANES;
123 }
124 while i < n {
125 out[i] = a[i] + b[i];
126 i += 1;
127 }
128 }
129
130 fn batch_sub(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
131 use crate::asm_presets::sub_f64x4;
132 const LANES: usize = 4;
133 let n = out.len();
134 let mut i = 0;
135 while i + LANES <= n {
136 sub_f64x4::apply(&a[i..i + LANES], &b[i..i + LANES], &mut out[i..i + LANES]);
137 i += LANES;
138 }
139 while i < n {
140 out[i] = a[i] - b[i];
141 i += 1;
142 }
143 }
144
145 fn batch_mul(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
146 use crate::asm_presets::mul_f64x4;
147 const LANES: usize = 4;
148 let n = out.len();
149 let mut i = 0;
150 while i + LANES <= n {
151 mul_f64x4::apply(&a[i..i + LANES], &b[i..i + LANES], &mut out[i..i + LANES]);
152 i += LANES;
153 }
154 while i < n {
155 out[i] = a[i] * b[i];
156 i += 1;
157 }
158 }
159
160 fn batch_div(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
161 use crate::asm_presets::div_f64x4;
162 const LANES: usize = 4;
163 let n = out.len();
164 let mut i = 0;
165 while i + LANES <= n {
166 div_f64x4::apply(&a[i..i + LANES], &b[i..i + LANES], &mut out[i..i + LANES]);
167 i += LANES;
168 }
169 while i < n {
170 out[i] = a[i] / b[i];
171 i += 1;
172 }
173 }
174
175 fn batch_fma(&self, a: &[f64], b: &[f64], c: &[f64], out: &mut [f64]) {
176 use crate::asm_presets::fma_f64x4;
177 const LANES: usize = 4;
178 let len = out.len();
179 let mut idx = 0;
180 while idx + LANES <= len {
181 fma_f64x4::apply(
182 &a[idx..idx + LANES],
183 &b[idx..idx + LANES],
184 &c[idx..idx + LANES],
185 &mut out[idx..idx + LANES],
186 );
187 idx += LANES;
188 }
189 while idx < len {
190 out[idx] = a[idx].mul_add(b[idx], c[idx]);
191 idx += 1;
192 }
193 }
194
195 fn batch_sqrt(&self, a: &[f64], out: &mut [f64]) {
196 use crate::asm_presets::sqrt_f64x4;
197 const LANES: usize = 4;
198 let n = out.len();
199 let mut i = 0;
200 while i + LANES <= n {
201 sqrt_f64x4::apply(&a[i..i + LANES], &mut out[i..i + LANES]);
202 i += LANES;
203 }
204 while i < n {
205 out[i] = a[i].sqrt();
206 i += 1;
207 }
208 }
209
210 fn batch_neg(&self, a: &[f64], out: &mut [f64]) {
211 use crate::asm_presets::neg_f64x4;
212 const LANES: usize = 4;
213 let n = out.len();
214 let mut i = 0;
215 while i + LANES <= n {
216 neg_f64x4::apply(&a[i..i + LANES], &mut out[i..i + LANES]);
217 i += LANES;
218 }
219 while i < n {
220 out[i] = -a[i];
221 i += 1;
222 }
223 }
224
225 fn batch_abs(&self, a: &[f64], out: &mut [f64]) {
226 use crate::asm_presets::abs_f64x4;
227 const LANES: usize = 4;
228 let n = out.len();
229 let mut i = 0;
230 while i + LANES <= n {
231 abs_f64x4::apply(&a[i..i + LANES], &mut out[i..i + LANES]);
232 i += LANES;
233 }
234 while i < n {
235 out[i] = a[i].abs();
236 i += 1;
237 }
238 }
239
240 fn name(&self) -> &'static str {
241 "avx2"
242 }
243}
244
245#[cfg(target_arch = "aarch64")]
251pub struct NeonKernel;
252
253#[cfg(target_arch = "aarch64")]
254impl SimdKernel for NeonKernel {
255 fn batch_add(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
256 use crate::asm_presets::add_f64x2;
257 const LANES: usize = 2;
258 let n = out.len();
259 let mut i = 0;
260 while i + LANES <= n {
261 let a2: &[f64; 2] = (&a[i..i + LANES]).try_into().unwrap();
262 let b2: &[f64; 2] = (&b[i..i + LANES]).try_into().unwrap();
263 let o2: &mut [f64; 2] = (&mut out[i..i + LANES]).try_into().unwrap();
264 add_f64x2::apply(a2, b2, o2);
265 i += LANES;
266 }
267 while i < n {
268 out[i] = a[i] + b[i];
269 i += 1;
270 }
271 }
272
273 fn batch_sub(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
274 use crate::asm_presets::sub_f64x2;
275 const LANES: usize = 2;
276 let n = out.len();
277 let mut i = 0;
278 while i + LANES <= n {
279 let a2: &[f64; 2] = (&a[i..i + LANES]).try_into().unwrap();
280 let b2: &[f64; 2] = (&b[i..i + LANES]).try_into().unwrap();
281 let o2: &mut [f64; 2] = (&mut out[i..i + LANES]).try_into().unwrap();
282 sub_f64x2::apply(a2, b2, o2);
283 i += LANES;
284 }
285 while i < n {
286 out[i] = a[i] - b[i];
287 i += 1;
288 }
289 }
290
291 fn batch_mul(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
292 use crate::asm_presets::mul_f64x2;
293 const LANES: usize = 2;
294 let n = out.len();
295 let mut i = 0;
296 while i + LANES <= n {
297 let a2: &[f64; 2] = (&a[i..i + LANES]).try_into().unwrap();
298 let b2: &[f64; 2] = (&b[i..i + LANES]).try_into().unwrap();
299 let o2: &mut [f64; 2] = (&mut out[i..i + LANES]).try_into().unwrap();
300 mul_f64x2::apply(a2, b2, o2);
301 i += LANES;
302 }
303 while i < n {
304 out[i] = a[i] * b[i];
305 i += 1;
306 }
307 }
308
309 fn batch_div(&self, a: &[f64], b: &[f64], out: &mut [f64]) {
310 use crate::asm_presets::div_f64x2;
311 const LANES: usize = 2;
312 let n = out.len();
313 let mut i = 0;
314 while i + LANES <= n {
315 let a2: &[f64; 2] = (&a[i..i + LANES]).try_into().unwrap();
316 let b2: &[f64; 2] = (&b[i..i + LANES]).try_into().unwrap();
317 let o2: &mut [f64; 2] = (&mut out[i..i + LANES]).try_into().unwrap();
318 div_f64x2::apply(a2, b2, o2);
319 i += LANES;
320 }
321 while i < n {
322 out[i] = a[i] / b[i];
323 i += 1;
324 }
325 }
326
327 fn batch_fma(&self, a: &[f64], b: &[f64], c: &[f64], out: &mut [f64]) {
328 for i in 0..out.len() {
330 out[i] = a[i].mul_add(b[i], c[i]);
331 }
332 }
333
334 fn batch_sqrt(&self, a: &[f64], out: &mut [f64]) {
335 use crate::asm_presets::sqrt_f64x2;
336 const LANES: usize = 2;
337 let n = out.len();
338 let mut i = 0;
339 while i + LANES <= n {
340 let a2: &[f64; 2] = (&a[i..i + LANES]).try_into().unwrap();
341 let o2: &mut [f64; 2] = (&mut out[i..i + LANES]).try_into().unwrap();
342 sqrt_f64x2::apply(a2, o2);
343 i += LANES;
344 }
345 while i < n {
346 out[i] = a[i].sqrt();
347 i += 1;
348 }
349 }
350
351 fn batch_neg(&self, a: &[f64], out: &mut [f64]) {
352 use crate::asm_presets::neg_f64x2;
353 const LANES: usize = 2;
354 let n = out.len();
355 let mut i = 0;
356 while i + LANES <= n {
357 let a2: &[f64; 2] = (&a[i..i + LANES]).try_into().unwrap();
358 let o2: &mut [f64; 2] = (&mut out[i..i + LANES]).try_into().unwrap();
359 neg_f64x2::apply(a2, o2);
360 i += LANES;
361 }
362 while i < n {
363 out[i] = -a[i];
364 i += 1;
365 }
366 }
367
368 fn batch_abs(&self, a: &[f64], out: &mut [f64]) {
369 use crate::asm_presets::abs_f64x2;
370 const LANES: usize = 2;
371 let n = out.len();
372 let mut i = 0;
373 while i + LANES <= n {
374 let a2: &[f64; 2] = (&a[i..i + LANES]).try_into().unwrap();
375 let o2: &mut [f64; 2] = (&mut out[i..i + LANES]).try_into().unwrap();
376 abs_f64x2::apply(a2, o2);
377 i += LANES;
378 }
379 while i < n {
380 out[i] = a[i].abs();
381 i += 1;
382 }
383 }
384
385 fn name(&self) -> &'static str {
386 "neon"
387 }
388}
389
390static GLOBAL_KERNEL: OnceLock<Box<dyn SimdKernel>> = OnceLock::new();
395
396pub fn global_kernel() -> &'static dyn SimdKernel {
404 GLOBAL_KERNEL.get_or_init(select_best_kernel).as_ref()
405}
406
407fn select_best_kernel() -> Box<dyn SimdKernel> {
408 #[cfg(target_arch = "x86_64")]
409 if crate::simd::detect::has_avx2() {
410 return Box::new(Avx2Kernel);
411 }
412
413 #[cfg(target_arch = "aarch64")]
414 if crate::simd::detect::has_neon() {
415 return Box::new(NeonKernel);
416 }
417
418 Box::new(ScalarKernel)
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn scalar_kernel_add() {
427 let a = [1.0_f64, 2.0, 3.0, 4.0];
428 let b = [10.0_f64; 4];
429 let mut out = [0.0_f64; 4];
430 ScalarKernel.batch_add(&a, &b, &mut out);
431 assert_eq!(out, [11.0, 12.0, 13.0, 14.0]);
432 }
433
434 #[test]
435 fn scalar_kernel_fma() {
436 let a = [2.0_f64, 3.0];
437 let b = [4.0_f64, 5.0];
438 let c = [1.0_f64, 1.0];
439 let mut out = [0.0_f64; 2];
440 ScalarKernel.batch_fma(&a, &b, &c, &mut out);
441 assert_eq!(out, [9.0, 16.0]);
442 }
443
444 #[test]
445 fn global_kernel_returns_valid_name() {
446 let name = global_kernel().name();
447 assert!(!name.is_empty());
448 }
449
450 #[test]
451 fn global_kernel_add_matches_scalar() {
452 let a: Vec<f64> = (0..16).map(f64::from).collect();
453 let b = vec![1.0_f64; 16];
454 let mut out_global = vec![0.0_f64; 16];
455 let mut out_scalar = vec![0.0_f64; 16];
456 global_kernel().batch_add(&a, &b, &mut out_global);
457 ScalarKernel.batch_add(&a, &b, &mut out_scalar);
458 for i in 0..16 {
459 assert!(
460 (out_global[i] - out_scalar[i]).abs() < 1e-12,
461 "mismatch at {i}"
462 );
463 }
464 }
465}