Skip to main content

hermes_simd_intrinsics/
lib.rs

1//! Hardware intrinsics and backend-specific implementations of SIMD kernels.
2//!
3//! This crate provides concrete [`SimdKernel`](hermes_simd_core::kernel::SimdKernel)
4//! implementations for every
5//! supported architecture marker:
6//!
7//! | Marker          | ISA          | f32 lanes | f64 lanes |
8//! |-----------------|--------------|-----------|-----------|
9//! | [`Scalar`]      | scalar loop  | 4         | 2         |
10//! | [`Avx2`]        | x86 AVX2     | 8         | 4         |
11//! | [`Avx512`]      | x86 AVX-512F | 16        | 8         |
12//! | [`Neon`]        | AArch64 NEON | 4         | 2         |
13//! | [`SveArch`]    | AArch64 SVE shape, emulated | 16 | 8 |
14//!
15//! Optional crate-feature backends:
16//! - `wide` — wraps the [`wide`](https://docs.rs/wide) crate.
17//! - `portable-simd` — wraps nightly `std::simd`.
18
19#![cfg_attr(not(feature = "std"), no_std)]
20#![deny(missing_docs)]
21#![allow(
22    clippy::needless_range_loop,
23    clippy::missing_safety_doc,
24    clippy::new_without_default,
25    clippy::too_many_arguments,
26    clippy::manual_is_multiple_of,
27    clippy::missing_const_for_thread_local
28)]
29extern crate alloc;
30
31use hermes_simd_core::arch::SimdArch;
32
33/// Implements `SimdKernel<$t>` for `$arch` as a lane-emulated `[T; N]` backend.
34///
35/// Used for the `Scalar` marker and for `(type, arch)` pairs without native
36/// register support; each method is a per-lane loop the optimizer is free to
37/// auto-vectorize.
38#[macro_export]
39macro_rules! impl_emulated_kernel {
40    ($arch:ty, $t:ty, $lanes:expr, $cfg:meta) => {
41        #[$cfg]
42        impl hermes_simd_core::kernel::SimdKernel<$t> for $arch {
43            type Vector = [$t; $lanes];
44            type Mask = [bool; $lanes];
45            type IndexVector = [i32; $lanes];
46            const LANE_COUNT: usize = $lanes;
47            const UNROLL_FACTOR: usize = 4;
48
49            #[inline(always)]
50            unsafe fn load_aligned(ptr: *const $t) -> Self::Vector {
51                let mut v = [<$t as hermes_simd_core::scalar::NumericElement>::ZERO; $lanes];
52                core::ptr::copy_nonoverlapping(ptr, v.as_mut_ptr(), $lanes);
53                v
54            }
55
56            #[inline(always)]
57            unsafe fn load_unaligned(ptr: *const $t) -> Self::Vector {
58                let mut v = [<$t as hermes_simd_core::scalar::NumericElement>::ZERO; $lanes];
59                core::ptr::copy_nonoverlapping(ptr, v.as_mut_ptr(), $lanes);
60                v
61            }
62
63            #[inline(always)]
64            unsafe fn store_aligned(ptr: *mut $t, val: Self::Vector) {
65                core::ptr::copy_nonoverlapping(val.as_ptr(), ptr, $lanes);
66            }
67
68            #[inline(always)]
69            unsafe fn store_unaligned(ptr: *mut $t, val: Self::Vector) {
70                core::ptr::copy_nonoverlapping(val.as_ptr(), ptr, $lanes);
71            }
72
73            #[inline(always)]
74            unsafe fn add(a: Self::Vector, b: Self::Vector) -> Self::Vector {
75                core::array::from_fn(|i| a[i] + b[i])
76            }
77
78            #[inline(always)]
79            unsafe fn mul(a: Self::Vector, b: Self::Vector) -> Self::Vector {
80                core::array::from_fn(|i| a[i] * b[i])
81            }
82
83            #[inline(always)]
84            unsafe fn sub(a: Self::Vector, b: Self::Vector) -> Self::Vector {
85                core::array::from_fn(|i| a[i] - b[i])
86            }
87
88            #[inline(always)]
89            unsafe fn neg(a: Self::Vector) -> Self::Vector {
90                core::array::from_fn(|i| -a[i])
91            }
92
93            #[inline(always)]
94            unsafe fn fmadd(a: Self::Vector, b: Self::Vector, c: Self::Vector) -> Self::Vector {
95                core::array::from_fn(|i| {
96                    <$t as hermes_simd_core::scalar::NumericElement>::scalar_fmadd(a[i], b[i], c[i])
97                })
98            }
99
100            #[inline(always)]
101            unsafe fn sum_reduce(v: Self::Vector) -> $t {
102                v.iter().copied().fold(
103                    <$t as hermes_simd_core::scalar::NumericElement>::ZERO,
104                    |acc, x| acc + x,
105                )
106            }
107
108            // masked_load_unaligned / masked_store_unaligned / masked_add /
109            // masked_mul / masked_fmadd / masked_sum_reduce are inherited from the
110            // `SimdKernel` scalar-emulated defaults (blend / generic_masked_*),
111            // which are bit-identical to the per-element loops they replaced.
112
113            #[inline(always)]
114            unsafe fn compress(src: Self::Vector, mask: Self::Mask) -> Self::Vector {
115                let mut out = [<$t as hermes_simd_core::scalar::NumericElement>::ZERO; $lanes];
116                let mut k = 0;
117                for i in 0..$lanes {
118                    if mask[i] {
119                        out[k] = src[i];
120                        k += 1;
121                    }
122                }
123                out
124            }
125
126            #[inline(always)]
127            unsafe fn expand(
128                src: Self::Vector,
129                mask: Self::Mask,
130                fill: Self::Vector,
131            ) -> Self::Vector {
132                let mut out = fill;
133                let mut k = 0;
134                for i in 0..$lanes {
135                    if mask[i] {
136                        out[i] = src[k];
137                        k += 1;
138                    }
139                }
140                out
141            }
142
143            #[inline(always)]
144            unsafe fn gather(base: *const $t, indices: Self::IndexVector) -> Self::Vector {
145                core::array::from_fn(|i| *base.add(indices[i] as usize))
146            }
147
148            #[inline(always)]
149            unsafe fn gather_masked(
150                base: *const $t,
151                indices: Self::IndexVector,
152                mask: Self::Mask,
153                src: Self::Vector,
154            ) -> Self::Vector {
155                core::array::from_fn(|i| {
156                    if mask[i] {
157                        *base.add(indices[i] as usize)
158                    } else {
159                        src[i]
160                    }
161                })
162            }
163
164            #[inline(always)]
165            unsafe fn mask_from_bools(bits: &[bool]) -> Self::Mask {
166                debug_assert_eq!(bits.len(), $lanes);
167                core::array::from_fn(|i| bits[i])
168            }
169
170            #[inline(always)]
171            unsafe fn leading_k_mask(k: usize) -> Self::Mask {
172                core::array::from_fn(|i| i < k)
173            }
174
175            #[inline(always)]
176            unsafe fn zero() -> Self::Vector {
177                [<$t as hermes_simd_core::scalar::NumericElement>::ZERO; $lanes]
178            }
179
180            #[inline(always)]
181            unsafe fn splat(val: $t) -> Self::Vector {
182                [val; $lanes]
183            }
184
185            #[inline(always)]
186            unsafe fn mask_to_bitmask(mask: Self::Mask) -> u64 {
187                let mut m = 0u64;
188                for i in 0..$lanes {
189                    if mask[i] {
190                        m |= 1u64 << i;
191                    }
192                }
193                m
194            }
195
196            #[inline(always)]
197            unsafe fn mask_to_vector(mask: Self::Mask) -> Self::Vector {
198                core::array::from_fn(|i| {
199                    if mask[i] {
200                        <$t as hermes_simd_core::scalar::NumericElement>::ALL_ONES
201                    } else {
202                        <$t as hermes_simd_core::scalar::NumericElement>::ZERO
203                    }
204                })
205            }
206
207            #[inline(always)]
208            unsafe fn vector_to_mask(v: Self::Vector) -> Self::Mask {
209                core::array::from_fn(|i| {
210                    // Masking with `SIGN_MASK` and counting bits keeps the test
211                    // bit-level for every element type: comparing the masked
212                    // lane against `ZERO` would misreport floating-point lanes,
213                    // whose sign-only pattern is `-0.0` and compares *equal* to
214                    // `ZERO` under IEEE semantics.
215                    <$t as hermes_simd_core::scalar::NumericElement>::count_ones(
216                        <$t as hermes_simd_core::scalar::NumericElement>::bitand(
217                            v[i],
218                            <$t as hermes_simd_core::scalar::NumericElement>::SIGN_MASK,
219                        ),
220                    ) != 0
221                })
222            }
223        }
224    };
225}
226
227pub mod aarch64;
228pub mod bitboard;
229pub mod scalar;
230pub mod x86_64;
231
232// Re-export SVE marker at crate root for ergonomic access.
233pub use aarch64::sve::SveArch;
234
235// Re-export bitboard backend markers.
236pub use bitboard::hybrid::HybridSwarMagic;
237pub use bitboard::hyperbola::Hyperbola;
238pub use bitboard::kogge_stone::KoggeStone;
239pub use bitboard::magic::Magic;
240pub use bitboard::swar::{Swar, SwarUtils};
241
242#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
243pub use x86_64::amx::{AmxBatchSession, AmxBf16, AmxConfig, AmxInt8, AmxSession, AmxSessionError};
244
245// ---------------------------------------------------------------------------
246// ZST Architecture Markers
247// ---------------------------------------------------------------------------
248
249/// Fallback scalar implementation marker.
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub struct Scalar;
252
253/// x86/x86_64 AVX2 instruction set architecture marker.
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub struct Avx2;
256
257/// x86/x86_64 AVX-512F instruction set architecture marker.
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub struct Avx512;
260
261/// x86/x86_64 AVX-VNNI (256-bit VEX-encoded VNNI) instruction set marker.
262///
263/// Distinct from [`Avx512`]: AVX-VNNI provides `vpdpbusd`/`vpdpwssd` on 256-bit
264/// YMM registers without requiring AVX-512, so it accelerates integer dot/GEMM
265/// on client CPUs (Alder Lake and newer) that lack AVX-512. The int8 tile kernel
266/// uses the unsigned-signed `vpdpbusd` with a `+128` operand bias correction
267/// (base AVX-VNNI has no signed-signed `vpdpbssd`; that is the separate
268/// `avxvnniint8` feature).
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub struct AvxVnni;
271
272/// AArch64 NEON instruction set architecture marker.
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub struct Neon;
275
276// ---------------------------------------------------------------------------
277// SimdArch impls
278// ---------------------------------------------------------------------------
279
280impl SimdArch for Scalar {
281    const NAME: &'static str = "scalar";
282    const REGISTER_WIDTH_BITS: u32 = 0;
283    const ISA_FAMILY: hermes_simd_core::arch::IsaFamily = hermes_simd_core::arch::IsaFamily::Scalar;
284    const FMA_THROUGHPUT_HINT: u32 = 1;
285
286    #[inline]
287    fn is_runtime_supported() -> bool {
288        true
289    }
290}
291
292impl SimdArch for Avx2 {
293    const NAME: &'static str = "avx2";
294    const REGISTER_WIDTH_BITS: u32 = 256;
295    const ISA_FAMILY: hermes_simd_core::arch::IsaFamily = hermes_simd_core::arch::IsaFamily::X86;
296    const FMA_THROUGHPUT_HINT: u32 = 4;
297
298    #[inline]
299    fn is_runtime_supported() -> bool {
300        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
301        {
302            std::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("fma")
303        }
304        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(feature = "std")))]
305        {
306            cfg!(target_feature = "avx2") && cfg!(target_feature = "fma")
307        }
308        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
309        {
310            false
311        }
312    }
313}
314
315impl SimdArch for Avx512 {
316    const NAME: &'static str = "avx512";
317    const REGISTER_WIDTH_BITS: u32 = 512;
318    const ISA_FAMILY: hermes_simd_core::arch::IsaFamily = hermes_simd_core::arch::IsaFamily::X86;
319    const FMA_THROUGHPUT_HINT: u32 = 8;
320
321    #[inline]
322    fn is_runtime_supported() -> bool {
323        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
324        {
325            std::is_x86_feature_detected!("avx512f")
326        }
327        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(feature = "std")))]
328        {
329            cfg!(target_feature = "avx512f")
330        }
331        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
332        {
333            false
334        }
335    }
336}
337
338impl SimdArch for AvxVnni {
339    const NAME: &'static str = "avx_vnni";
340    const REGISTER_WIDTH_BITS: u32 = 256;
341    const ISA_FAMILY: hermes_simd_core::arch::IsaFamily = hermes_simd_core::arch::IsaFamily::X86;
342    // Two 256-bit `vpdpbusd` per cycle on current client cores (Golden Cove / Zen 4).
343    const FMA_THROUGHPUT_HINT: u32 = 4;
344
345    #[inline]
346    fn is_runtime_supported() -> bool {
347        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
348        {
349            std::is_x86_feature_detected!("avxvnni")
350        }
351        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(feature = "std")))]
352        {
353            false
354        }
355        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
356        {
357            false
358        }
359    }
360}
361
362impl SimdArch for Neon {
363    const NAME: &'static str = "neon";
364    const REGISTER_WIDTH_BITS: u32 = 128;
365    const ISA_FAMILY: hermes_simd_core::arch::IsaFamily =
366        hermes_simd_core::arch::IsaFamily::AArch64;
367    const FMA_THROUGHPUT_HINT: u32 = 4;
368
369    #[inline]
370    fn is_runtime_supported() -> bool {
371        cfg!(target_arch = "aarch64")
372    }
373}
374
375impl hermes_simd_core::private::Sealed for Scalar {}
376impl hermes_simd_core::private::Sealed for Avx2 {}
377impl hermes_simd_core::private::Sealed for Avx512 {}
378impl hermes_simd_core::private::Sealed for AvxVnni {}
379impl hermes_simd_core::private::Sealed for Neon {}
380impl hermes_simd_core::private::Sealed for SveArch {}