Skip to main content

simd_kernels/kernels/arithmetic/
dispatch.rs

1// Copyright 2025 Peter Garfield Bower
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # **Arithmetic Dispatch Module** - *SIMD/Scalar Dispatch Layer for Arithmetic Operations*
16//!
17//! High-performance arithmetic kernel dispatcher that automatically selects between SIMD and scalar
18//! implementations based on data alignment and feature flags.
19//!
20//! ## Overview
21//! - **Dual-path execution**: SIMD-accelerated path with scalar fallback for unaligned data
22//! - **Type-specific dispatch**: Optimised kernels for integers (i32/i64/u32/u64), floats (f32/f64), and datetime types
23//! - **Null-aware operations**: Arrow-compatible null mask propagation and handling
24//! - **Build-time SIMD lanes**: Lane counts determined at build time based on target architecture
25//!
26//! ## Supported Operations  
27//! - **Basic arithmetic**: Add, subtract, multiply, divide, remainder, power
28//! - **Fused multiply-add (FMA)**: Hardware-accelerated `a * b + c` operations for floats
29//! - **Datetime arithmetic**: Temporal operations with integer kernel delegation
30//!
31//! ## Performance Strategy
32//! - SIMD requires 64-byte aligned input data. This is automatic with Vec64.
33//! - Scalar fallback ensures correctness regardless of input alignment
34
35include!(concat!(env!("OUT_DIR"), "/simd_lanes.rs"));
36
37#[cfg(feature = "datetime")]
38use crate::DatetimeAVT;
39#[cfg(feature = "datetime")]
40use crate::DatetimeArray;
41use crate::enums::error::KernelError;
42use crate::enums::operators::ArithmeticOperator::{self};
43#[cfg(feature = "simd")]
44use crate::kernels::arithmetic::simd::{
45    float_dense_body_f32_simd, float_dense_body_f64_simd, float_masked_body_f32_simd,
46    float_masked_body_f64_simd, fma_dense_body_f32_simd, fma_dense_body_f64_simd,
47    fma_masked_body_f32_simd, fma_masked_body_f64_simd, int_dense_body_simd, int_masked_body_simd,
48};
49use crate::kernels::arithmetic::std::{
50    float_dense_body_std, float_masked_body_std, int_dense_body_std, int_masked_body_std,
51};
52#[cfg(feature = "datetime")]
53use crate::kernels::bitmask::merge_bitmasks_to_new;
54use crate::structs::variants::float::FloatArray;
55use crate::structs::variants::integer::IntegerArray;
56use crate::utils::confirm_equal_len;
57#[cfg(feature = "simd")]
58use crate::utils::is_simd_aligned;
59use crate::{Bitmask, Vec64};
60// Kernels
61
62/// Generates element-wise integer arithmetic functions with SIMD/scalar dispatch.
63/// Creates functions that operate on `&[T]` slices, returning `IntegerArray<T>` with proper null handling.
64/// Automatically selects SIMD path for 64-byte aligned inputs, falls back to scalar otherwise.
65macro_rules! impl_apply_int {
66    ($fn_name:ident, $ty:ty, $lanes:expr) => {
67        #[doc = concat!(
68            "Performs element-wise integer `ArithmeticOperator` over two `&[", stringify!($ty),
69            "]`, SIMD-accelerated using ", stringify!($lanes), " lanes if available, \
70            otherwise falls back to scalar. \
71            Returns `IntegerArray<", stringify!($ty), ">` with appropriate null-mask handling."
72        )]
73        #[inline(always)]
74        pub fn $fn_name(
75            lhs: &[$ty],
76            rhs: &[$ty],
77            op: ArithmeticOperator,
78            mask: Option<&Bitmask>
79        ) -> Result<IntegerArray<$ty>, KernelError> {
80            let len = lhs.len();
81            confirm_equal_len("apply numeric: length mismatch", len, rhs.len())?;
82
83            #[cfg(feature = "simd")]
84            {
85                // Check if both arrays are 64-byte aligned for SIMD
86                if is_simd_aligned(lhs) && is_simd_aligned(rhs) {
87                    // SIMD path - safe because we verified alignment
88                    let mut out = Vec64::with_capacity(len);
89                    unsafe { out.set_len(len) };
90                    match mask {
91                        Some(mask) => {
92                            let mut out_mask = crate::Bitmask::new_set_all(len, true);
93                            int_masked_body_simd::<$ty, $lanes>(op, lhs, rhs, mask, &mut out, &mut out_mask);
94                            return Ok(IntegerArray {
95                                data: out.into(),
96                                null_mask: Some(out_mask),
97                            });
98                        }
99                        None => {
100                            int_dense_body_simd::<$ty, $lanes>(op, lhs, rhs, &mut out);
101                            return Ok(IntegerArray {
102                                data: out.into(),
103                                null_mask: None,
104                            });
105                        }
106                    }
107                }
108                // Fall through to scalar path if alignment check failed
109            }
110
111            // Scalar fallback - alignment check failed
112            let mut out = Vec64::with_capacity(len);
113            unsafe { out.set_len(len) };
114            match mask {
115                Some(mask) => {
116                    let mut out_mask = crate::Bitmask::new_set_all(len, true);
117                    int_masked_body_std::<$ty>(op, lhs, rhs, mask, &mut out, &mut out_mask);
118                    Ok(IntegerArray {
119                        data: out.into(),
120                        null_mask: Some(out_mask),
121                    })
122                }
123                None => {
124                    int_dense_body_std::<$ty>(op, lhs, rhs, &mut out);
125                    Ok(IntegerArray {
126                        data: out.into(),
127                        null_mask: None,
128                    })
129                }
130            }
131        }
132    };
133}
134
135/// Generates element-wise floating-point arithmetic functions with SIMD/scalar dispatch.
136/// Creates functions that operate on `&[T]` slices, returning `FloatArray<T>` with proper null handling.
137/// Supports hardware-accelerated operations including FMA when available.
138macro_rules! impl_apply_float {
139    ($fn_name:ident, $ty:ty, $lanes:expr, $dense_body_simd:ident, $masked_body_simd:ident) => {
140        #[doc = concat!(
141                    "Performs element-wise float `ArithmeticOperator` on `&[", stringify!($ty),
142                    "]` using SIMD (", stringify!($lanes), " lanes) for dense/masked cases,  \
143                    Falls back to standard scalar ops when the `simd` feature is not enabled. \
144            Returns `FloatArray<", stringify!($ty), ">` and handles optional null-mask."
145                )]
146        #[inline(always)]
147        pub fn $fn_name(
148            lhs: &[$ty],
149            rhs: &[$ty],
150            op: ArithmeticOperator,
151            mask: Option<&Bitmask>
152        ) -> Result<FloatArray<$ty>, KernelError> {
153            let len = lhs.len();
154            confirm_equal_len("apply numeric: length mismatch", len, rhs.len())?;
155
156            #[cfg(feature = "simd")]
157            {
158                // Check if both arrays are 64-byte aligned for SIMD
159                if is_simd_aligned(lhs) && is_simd_aligned(rhs) {
160                    // SIMD path - safe because we verified alignment
161                    let mut out = Vec64::with_capacity(len);
162                    unsafe { out.set_len(len) };
163                    match mask {
164                        Some(mask) => {
165                            let mut out_mask = crate::Bitmask::new_set_all(len, true);
166                            $masked_body_simd::<$lanes>(op, lhs, rhs, mask, &mut out, &mut out_mask);
167                            return Ok(FloatArray {
168                                data: out.into(),
169                                null_mask: Some(out_mask),
170                            });
171                        }
172                        None => {
173                            $dense_body_simd::<$lanes>(op, lhs, rhs, &mut out);
174                            return Ok(FloatArray {
175                                data: out.into(),
176                                null_mask: None,
177                            });
178                        }
179                    }
180                }
181                // Fall through to scalar path if alignment check failed
182            }
183
184            // Scalar fallback - alignment check failed
185            let mut out = Vec64::with_capacity(len);
186            unsafe { out.set_len(len) };
187            match mask {
188                Some(mask) => {
189                    let mut out_mask = crate::Bitmask::new_set_all(len, true);
190                    float_masked_body_std::<$ty>(op, lhs, rhs, mask, &mut out, &mut out_mask);
191                    Ok(FloatArray {
192                        data: out.into(),
193                        null_mask: Some(out_mask),
194                    })
195                }
196                None => {
197                    float_dense_body_std::<$ty>(op, lhs, rhs, &mut out);
198                    Ok(FloatArray {
199                        data: out.into(),
200                        null_mask: None,
201                    })
202                }
203            }
204        }
205    };
206}
207
208/// Generates fused multiply-add (FMA) functions with SIMD/scalar dispatch.
209/// Creates `a * b + c` operations on `&[T]` slices, returning `FloatArray<T>`.
210/// Uses `mul_add()`, which leverages hardware FMA when available.
211macro_rules! impl_apply_fma_float {
212    ($fn_name:ident, $ty:ty, $lanes:expr, $dense_simd:ident, $masked_simd:ident) => {
213        #[doc = concat!(
214            "Performs element-wise fused multiply-add (`a * b + acc`) on `&[", stringify!($ty),
215            "]` using SIMD (", stringify!($lanes), " lanes; dense or masked, via `",
216            stringify!($dense), "`/`", stringify!($masked), "` as needed. \
217            Falls back to standard scalar ops when the `simd` feature is not enabled. \
218            Results in a `FloatArray<", stringify!($ty), ">`."
219        )]
220        #[inline(always)]
221        pub fn $fn_name(
222            lhs: &[$ty],
223            rhs: &[$ty],
224            acc: &[$ty],
225            mask: Option<&Bitmask>
226        ) -> Result<FloatArray<$ty>, KernelError> {
227            let len = lhs.len();
228            confirm_equal_len("apply numeric: length mismatch", len, rhs.len())?;
229            confirm_equal_len("acc length mismatch", len, acc.len())?;
230
231            let mut out = Vec64::with_capacity(len);
232            unsafe { out.set_len(len) };
233            let mut out_mask = crate::Bitmask::new_set_all(len, true);
234
235            #[cfg(feature = "simd")]
236            {
237                // Check if all arrays are properly aligned for SIMD (cheap runtime check)
238                if is_simd_aligned(lhs) && is_simd_aligned(rhs) && is_simd_aligned(acc) {
239                    // SIMD path - safe because we verified alignment
240                    match mask {
241                        Some(mask) => {
242                            $masked_simd::<$lanes>(lhs, rhs, acc, mask, &mut out, &mut out_mask);
243                            return Ok(FloatArray {
244                                data: out.into(),
245                                null_mask: Some(out_mask),
246                            });
247                        }
248                        None => {
249                            $dense_simd::<$lanes>(lhs, rhs, acc, &mut out);
250                            return Ok(FloatArray {
251                                data: out.into(),
252                                null_mask: None,
253                            });
254                        }
255                    }
256                }
257                // Fall through to scalar path if alignment check failed
258            }
259
260            // Scalar fallback - alignment check failed
261            match mask {
262                Some(mask) => {
263                    // Masked FMA: a * b + acc with null handling
264                    for i in 0..len {
265                        if unsafe { mask.get_unchecked(i) } {
266                            out[i] = lhs[i] * rhs[i] + acc[i];
267                        } else {
268                            out[i] = 0 as $ty;  // Initialize masked values to zero
269                            out_mask.set(i, false);
270                        }
271                    }
272                    Ok(FloatArray {
273                        data: out.into(),
274                        null_mask: Some(out_mask),
275                    })
276                }
277                None => {
278                    // Dense FMA: a * b + acc
279                    for i in 0..len {
280                        out[i] = lhs[i] * rhs[i] + acc[i];
281                    }
282                    Ok(FloatArray {
283                        data: out.into(),
284                        null_mask: None,
285                    })
286                }
287            }
288        }
289    };
290}
291
292/// Performs element-wise arithmetic between two `DatetimeArray<T>`s with SIMD/scalar fallback,
293/// using the standard integer SIMD/scalar kernels for the underlying data.
294///
295/// Returns `DatetimeArray<T>` with correct null propagation.
296///
297/// # Supported operations
298/// - Add, Subtract, Multiply, Divide, Remainder
299/// - Power: defined as left-value (lhs) preserved (see notes)
300///
301/// # Notes
302/// - **"Power" for dates/times is undefined: returns lhs unchanged**.
303/// - All other ops delegate directly to the integer kernels for correctness/performance.
304/// - Any future date-specific ops should be implemented in the stub below.
305/// Generates datetime arithmetic functions with SIMD/scalar dispatch.
306/// Creates functions that operate on `DatetimeArray<T>` types, delegating to integer kernels
307/// for the underlying temporal data while preserving datetime semantics.
308#[cfg(feature = "datetime")]
309macro_rules! impl_apply_datetime {
310    ($fn_name:ident, $ty:ty, $lanes:expr) => {
311        #[inline(always)]
312        pub fn $fn_name(
313            lhs: DatetimeAVT<$ty>,
314            rhs: DatetimeAVT<$ty>,
315            op: ArithmeticOperator,
316        ) -> Result<DatetimeArray<$ty>, KernelError> {
317            let (larr, loff, llen) = lhs;
318            let (rarr, roff, rlen) = rhs;
319            confirm_equal_len("apply_datetime: length mismatch", llen, rlen)?;
320
321            let out_mask =
322                merge_bitmasks_to_new(larr.null_mask.as_ref(), rarr.null_mask.as_ref(), llen);
323            let ldata = &larr.data[loff..loff + llen];
324            let rdata = &rarr.data[roff..roff + rlen];
325
326            let mut out = Vec64::<$ty>::with_capacity(llen);
327            unsafe {
328                out.set_len(llen);
329            }
330
331            match out_mask.as_ref() {
332                Some(mask) => {
333                    let mut result_mask = crate::Bitmask::new_set_all(llen, true);
334                    #[cfg(feature = "simd")]
335                    {
336                        int_masked_body_simd::<$ty, $lanes>(
337                            op,
338                            ldata,
339                            rdata,
340                            mask,
341                            &mut out,
342                            &mut result_mask,
343                        );
344                    }
345                    #[cfg(not(feature = "simd"))]
346                    {
347                        int_masked_body_std::<$ty>(
348                            op,
349                            ldata,
350                            rdata,
351                            mask,
352                            &mut out,
353                            &mut result_mask,
354                        );
355                    }
356                    Ok(DatetimeArray::from_vec64(out, Some(result_mask), None))
357                }
358                None => {
359                    #[cfg(feature = "simd")]
360                    {
361                        int_dense_body_simd::<$ty, $lanes>(op, ldata, rdata, &mut out);
362                    }
363                    #[cfg(not(feature = "simd"))]
364                    {
365                        int_dense_body_std::<$ty>(op, ldata, rdata, &mut out);
366                    }
367                    Ok(DatetimeArray::from_vec64(out, None, None))
368                }
369            }
370        }
371    };
372}
373
374// Generates i32, u32, i64, u64, f32, f64 variants using lane counts via simd_lanes.rs
375
376impl_apply_int!(apply_int_i32, i32, W32);
377impl_apply_int!(apply_int_u32, u32, W32);
378impl_apply_int!(apply_int_i64, i64, W64);
379impl_apply_int!(apply_int_u64, u64, W64);
380#[cfg(feature = "extended_numeric_types")]
381impl_apply_int!(apply_int_i16, i16, W16);
382#[cfg(feature = "extended_numeric_types")]
383impl_apply_int!(apply_int_u16, u16, W16);
384#[cfg(feature = "extended_numeric_types")]
385impl_apply_int!(apply_int_i8, i8, W8);
386#[cfg(feature = "extended_numeric_types")]
387impl_apply_int!(apply_int_u8, u8, W8);
388
389impl_apply_float!(
390    apply_float_f32,
391    f32,
392    W32,
393    float_dense_body_f32_simd,
394    float_masked_body_f32_simd
395);
396impl_apply_float!(
397    apply_float_f64,
398    f64,
399    W64,
400    float_dense_body_f64_simd,
401    float_masked_body_f64_simd
402);
403
404impl_apply_fma_float!(
405    apply_fma_f32,
406    f32,
407    W32,
408    fma_dense_body_f32_simd,
409    fma_masked_body_f32_simd
410);
411
412impl_apply_fma_float!(
413    apply_fma_f64,
414    f64,
415    W64,
416    fma_dense_body_f64_simd,
417    fma_masked_body_f64_simd
418);
419
420#[cfg(feature = "datetime")]
421impl_apply_datetime!(apply_datetime_i32, i32, W32);
422#[cfg(feature = "datetime")]
423impl_apply_datetime!(apply_datetime_u32, u32, W32);
424#[cfg(feature = "datetime")]
425impl_apply_datetime!(apply_datetime_i64, i64, W64);
426#[cfg(feature = "datetime")]
427impl_apply_datetime!(apply_datetime_u64, u64, W64);