Skip to main content

fearless_simd/
lib.rs

1// Copyright 2024 the Fearless_SIMD Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4// After you edit the crate's doc comment, run this command, then check README.md for any missing links
5// cargo rdme --workspace-project=fearless_simd
6
7//! `fearless_simd` takes `unsafe` out of SIMD.
8//!
9//! No matter what level of abstraction you're after, be it autovectorization and multiversioning, or portable SIMD, or safe access to raw
10//! intrinsics and nothing more, `fearless_simd` has you covered!
11//!
12//! Zero dependencies, from-scratch build time under 1 second, safe public APIs, and [very little](https://gist.github.com/Shnatsel/61fc294987a1e051ce3835c97dc0fc19) `unsafe` under the hood.
13//!
14//! # Automatic vectorization
15//!
16//! Put the code to vectorize in an `#[inline(always)]` function generic over [`Simd`].
17//!
18//! This will generate several implementations for different SIMD levels and select the best one at runtime:
19//!
20//! ```rust
21//! use fearless_simd::{dispatch, Level, Simd};
22//!
23//! #[inline(always)]
24//! fn double_u32s<S: Simd>(_: S, values: &mut [u32]) {
25//!     for value in values {
26//!         *value = *value * 2;
27//!     }
28//! }
29//!
30//! let mut values = [1, 2, 3, 4, 5];
31//! let level = Level::new(); // Detect SIMD available on the CPU. Expensive, so do it once.
32//! dispatch!(level, simd => double_u32s(simd, &mut values));
33//! assert_eq!(values, [2, 4, 6, 8, 10]);
34//! ```
35//!
36//! # Portable SIMD
37//!
38//! Use the vector types for explicit lane-wise operations while staying generic over the SIMD level:
39//!
40//! ```rust
41//! use fearless_simd::{dispatch, prelude::*, Level};
42//!
43//! #[inline(always)]
44//! fn double_u32s<S: Simd>(simd: S, values: &mut [u32]) {
45//!     let mut chunks = values.chunks_exact_mut(S::u32s::N); // the CPU's native SIMD width
46//!     for chunk in &mut chunks {
47//!         let v = S::u32s::from_slice(simd, chunk);
48//!         (v * 2).store_slice(chunk);
49//!     }
50//!     for value in chunks.into_remainder() {
51//!         *value = *value * 2;
52//!     }
53//! }
54//!
55//! let mut values = [1, 2, 3, 4, 5];
56//! let level = Level::new(); // Detect SIMD available on the CPU. Expensive, so do it once.
57//! dispatch!(level, simd => double_u32s(simd, &mut values));
58//! assert_eq!(values, [2, 4, 6, 8, 10]);
59//! ```
60//!
61//! You can also use fixed-size types such as [u32x8] instead of using the hardware's native SIMD width.
62//!
63//! # Explicit intrinsics
64//!
65//! If you need access to raw intrinsics, [`kernel!`][kernel] creates a function where they can be called safely:
66//!
67//! ```rust
68//! use fearless_simd::{prelude::*, Level, u32x4};
69//!
70//! fearless_simd::kernel!(
71//!     fn double_u32s_neon(neon: Neon, values: &mut [u32]) {
72//!         use core::arch::aarch64::*;
73//!
74//!         let mut chunks = values.chunks_exact_mut(4);
75//!         for chunk in &mut chunks {
76//!             let v: uint32x4_t = u32x4::from_slice(neon, chunk).into(); // safe load
77//!             let doubled = vmulq_u32(v, vdupq_n_u32(2)); // safe access to a NEON intrinsic
78//!             let doubled: u32x4<_> = doubled.simd_into(neon);
79//!             doubled.store_slice(chunk);
80//!         }
81//!         for value in chunks.into_remainder() {
82//!             *value = *value * 2;
83//!         }
84//!     }
85//! );
86//!
87//! #[cfg(target_arch = "aarch64")]
88//! {
89//!     let level = Level::new(); // Detect SIMD available on the CPU. Expensive, so do it once.
90//!     if let Some(neon) = level.as_neon() {
91//!         let mut values = [1, 2, 3, 4, 5];
92//!         double_u32s_neon(neon, &mut values);
93//!         assert_eq!(values, [2, 4, 6, 8, 10]);
94//!     }
95//! }
96//! ```
97//!
98//! You can also [mix and match](https://github.com/linebender/fearless_simd/blob/main/fearless_simd/examples/srgb.rs)
99//! intrinsics with the other approaches, using high-level code most of the time and dropping down to
100//! hardware-specific intrinsics only when necessary.
101//!
102//! # Inlining
103//!
104//! Fearless SIMD relies heavily on Rust's inlining support to create functions which have the given target features enabled.
105//!
106//! As a rule of thumb:
107//!
108//! - All SIMD functions need `#[inline(always)]`.
109//! - Use [`dispatch`] when calling SIMD code from non-SIMD code.
110//! - Use [`vectorize()`][Simd::vectorize] when calling SIMD from SIMD if you don't want to force inlining.
111//!
112//! [The article describing the design](https://gist.github.com/Shnatsel/61fc294987a1e051ce3835c97dc0fc19#the-abi-would-like-a-word) covers why this is the
113//! case. There's also Q&A on [Zulip](https://xi.zulipchat.com/#narrow/channel/514230-simd/topic/inlining/with/546913433).
114//!
115//! # Instruction set support
116//!
117//! - x86/x86-64: [v2](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels) (SSE4.2), [v3](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels) (AVX2), [Ice Lake](https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512) (AVX-512, avoiding early slow implementations)
118//! - Aarch64: Baseline [NEON](https://en.wikipedia.org/wiki/Arm_architecture_family#Advanced_SIMD_(Neon))
119//! - WebAssembly: [128-bit packed SIMD](https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md), [relaxed SIMD](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md)
120//!
121//! A scalar fallback is also provided for platforms, so your code still works even if SIMD is not available.
122//!
123//! # WebAssembly
124//!
125//! WASM SIMD doesn't have feature detection, and so you need to compile two versions of your bundle for WASM, one with SIMD and one without,
126//! then select the appropriate one for your user's browser. This can be done via [the `wasm-feature-detect`
127//! library](https://github.com/GoogleChromeLabs/wasm-feature-detect).
128//!
129//! You can compile WebAssembly with the SIMD128 feature enabled via the `RUSTFLAGS` environment variable
130//! (`RUSTFLAGS="-Ctarget-feature=+simd128"`), or by adding the compiler flags in your [Cargo
131//! config.toml](https://doc.rust-lang.org/cargo/reference/config.html):
132//!
133//! ```toml
134//! [target.'cfg(target_arch = "wasm32")']
135//! rustflags = ["-Ctarget-feature=+simd128"]
136//! rustdocflags = ["-Ctarget-feature=+simd128"]
137//! ```
138//!
139//! If you want to compile both SIMD and non-SIMD versions of your WebAssembly library, your best option right now is to create a shell script
140//! that builds it once with the `RUSTFLAGS` specified, and once without. [Cargo currently does not allow specifying compiler flags
141//! per-profile.](https://github.com/rust-lang/cargo/issues/10271)
142//!
143//! ## Relaxed SIMD
144//!
145//! Fearless SIMD can make use of the [relaxed SIMD](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md)
146//! WebAssembly instructions, if the requisite target feature is enabled. These instructions can return implementation-dependent results
147//! depending on what is fastest on the underlying hardware. They are only used for operations where we already give hardware-dependent results.
148//!
149//! At the time of writing, relaxed SIMD is only supported in Chrome. To make use of it, you'll need to build two versions of your library, one
150//! with relaxed SIMD enabled (`RUSTFLAGS="-Ctarget-feature=+simd128,+relaxed-simd"`) and one with it disabled, and then feature-detect at
151//! runtime.
152//!
153//! # Multiversioning on x86
154//!
155//! x86 CPUs are not guaranteed to have any SIMD particular instruction set, so `fearless_simd` compiles a version
156//! of each function generic over [`Simd`] for each instruction set, and [`dispatch`] selects the best one at runtime.
157//!
158//! This is necessary to take advantage of SIMD, but results in an increased binary size on x86.
159//! If binary size is a concern, the increase can be partially mitigated by setting
160//! [`codegen-units=1`](https://nnethercote.github.io/perf-book/build-configuration.html#codegen-units)
161//! or [`lto=true`](https://nnethercote.github.io/perf-book/build-configuration.html#link-time-optimization) in your Cargo.toml,
162//! at the cost of longer build times.
163//!
164//! As a last resort, you can turn off multiversioning for specific SIMD instruction sets by passing
165//! `--cfg disable_dispatch_sse4_2`, `--cfg disable_dispatch_avx2`, or `--cfg disable_dispatch_avx512` in `RUSTFLAGS`.
166//! These configuration flags only control automatic multiversioning. Disabling one does not remove its token type, its
167//! [`Simd`] implementation, or explicit [`kernel`] support; for example, an `Avx2` token can still be used to call an
168//! AVX2 kernel when the CPU supports it.
169//!
170//! Note that later extensions can be beneficial even if you are only using 128-bit vectors:
171//! AVX2 and AVX-512 provide more efficient instructions for some operations,
172//! and AVX-512 also more than doubles the number of vector registers of all sizes.
173//!
174//! You can also [disable certain instruction sets for select functions](https://github.com/linebender/fearless_simd/blob/main/fearless_simd/examples/sigmoid.rs)
175//! without disabling them globally.
176//!
177//! # Feature Flags
178//!
179//! The following crate [feature flags](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features) are available:
180//!
181//! - `std` (enabled by default): Get floating point functions from the standard library (likely using your target's libc).
182//!   Also allows using [`Level::new`] on all platforms, to detect which target features are enabled.
183//! - `libm`: Use floating point implementations from [libm]. Useful for `#[no_std]`.
184//! - `force_support_fallback`: Force scalar fallback, to be supported, even if your compilation target has a better baseline.
185//!
186//! At least one of `std` and `libm` is required; `std` overrides `libm`.
187//!
188//! # Credits
189//!
190//! This crate was inspired by [`pulp`], [`std::simd`], among others in the Rust ecosystem, though makes many decisions differently.
191//! It benefited from conversations with Luca Versari, though he is not responsible for any of the mistakes or bad decisions.
192//!
193//! [`pulp`]: https://crates.io/crates/pulp
194// LINEBENDER LINT SET - lib.rs - v3
195// See https://linebender.org/wiki/canonical-lints/
196// These lints shouldn't apply to examples or tests.
197#![cfg_attr(not(test), warn(unused_crate_dependencies))]
198// These lints shouldn't apply to examples.
199#![warn(clippy::print_stdout, clippy::print_stderr)]
200// Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
201#![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
202// END LINEBENDER LINT SET
203#![cfg_attr(not(test), deny(clippy::disallowed_methods))]
204#![cfg_attr(docsrs, feature(doc_cfg))]
205#![allow(non_camel_case_types, reason = "TODO")]
206#![expect(clippy::unused_unit, reason = "easier for code generation")]
207#![no_std]
208
209#[cfg(feature = "std")]
210extern crate std;
211
212#[cfg(all(not(feature = "libm"), not(feature = "std")))]
213compile_error!("fearless_simd requires either the `std` or `libm` feature");
214
215// Suppress the unused_crate_dependencies lint when both std and libm are specified.
216#[cfg(all(feature = "std", feature = "libm"))]
217use libm as _;
218
219mod generated;
220mod kernel_macros;
221mod macros;
222mod support;
223mod traits;
224mod transmute;
225
226pub use generated::*;
227pub use traits::*;
228
229/// This prelude module re-exports every SIMD trait defined in this library. It's useful for accessing trait methods.
230///
231/// Only traits are exported through the prelude; types must be exported separately.
232pub mod prelude {
233    pub use crate::generated::simd_trait::*;
234    pub use crate::traits::*;
235}
236
237/// Implementations of [`Simd`] for 64 bit ARM.
238#[cfg(target_arch = "aarch64")]
239pub mod aarch64 {
240    pub use crate::generated::Neon;
241}
242
243/// Implementations of [`Simd`] for webassembly.
244#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
245pub mod wasm32 {
246    pub use crate::generated::WasmSimd128;
247}
248
249/// Implementations of [`Simd`] on x86 architectures (both 32 and 64 bit).
250#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
251pub mod x86 {
252    pub use crate::generated::Avx2;
253    pub use crate::generated::Avx512;
254    pub use crate::generated::Sse4_2;
255}
256
257// Sourced from `rustc --print=cfg --target x86_64-unknown-linux-gnu -C target-cpu=icelake-server`
258// and pruned against the features implied by `avx512f` which can be viewed via
259// `rustc --print=cfg --target x86_64-unknown-linux-gnu -C target-feature='+avx2'`
260#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
261#[inline]
262fn x86_detects_icelake_avx512() -> bool {
263    std::arch::is_x86_feature_detected!("adx")
264        && std::arch::is_x86_feature_detected!("aes")
265        && std::arch::is_x86_feature_detected!("avx512bitalg")
266        && std::arch::is_x86_feature_detected!("avx512bw")
267        && std::arch::is_x86_feature_detected!("avx512cd")
268        && std::arch::is_x86_feature_detected!("avx512dq")
269        && std::arch::is_x86_feature_detected!("avx512f")
270        && std::arch::is_x86_feature_detected!("avx512ifma")
271        && std::arch::is_x86_feature_detected!("avx512vbmi")
272        && std::arch::is_x86_feature_detected!("avx512vbmi2")
273        && std::arch::is_x86_feature_detected!("avx512vl")
274        && std::arch::is_x86_feature_detected!("avx512vnni")
275        && std::arch::is_x86_feature_detected!("avx512vpopcntdq")
276        && std::arch::is_x86_feature_detected!("bmi1")
277        && std::arch::is_x86_feature_detected!("bmi2")
278        && std::arch::is_x86_feature_detected!("cmpxchg16b")
279        && std::arch::is_x86_feature_detected!("fma")
280        && std::arch::is_x86_feature_detected!("gfni")
281        && std::arch::is_x86_feature_detected!("lzcnt")
282        && std::arch::is_x86_feature_detected!("movbe")
283        && std::arch::is_x86_feature_detected!("pclmulqdq")
284        && std::arch::is_x86_feature_detected!("popcnt")
285        && std::arch::is_x86_feature_detected!("rdrand")
286        && std::arch::is_x86_feature_detected!("rdseed")
287        && std::arch::is_x86_feature_detected!("sha")
288        && std::arch::is_x86_feature_detected!("vaes")
289        && std::arch::is_x86_feature_detected!("vpclmulqdq")
290        && std::arch::is_x86_feature_detected!("xsave")
291        && std::arch::is_x86_feature_detected!("xsavec")
292        && std::arch::is_x86_feature_detected!("xsaveopt")
293        && std::arch::is_x86_feature_detected!("xsaves")
294}
295
296/// The level enum with the specific SIMD capabilities available.
297///
298/// The contained values serve as a proof that the associated target
299/// feature is available.
300#[derive(Clone, Copy, Debug)]
301#[non_exhaustive]
302pub enum Level {
303    /// Scalar fallback level, i.e. no supported SIMD features are to be used.
304    ///
305    /// This can be created with [`Level::fallback`].
306    Fallback(Fallback),
307    /// The Neon instruction set on 64 bit ARM.
308    #[cfg(target_arch = "aarch64")]
309    Neon(Neon),
310    /// The SIMD 128 instructions on 32-bit WebAssembly.
311    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
312    WasmSimd128(WasmSimd128),
313    /// The SSE4.2 instruction set on (32 and 64 bit) x86, plus `popcnt` and `cmpxchg16b`.
314    /// Also known as x86-64-v2.
315    ///
316    /// All production CPUs with SSE4.2 also support the other two extensions, so it is safe to require them.
317    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
318    Sse4_2(Sse4_2),
319    /// Ice Lake-class AVX-512 on (32 and 64 bit) x86.
320    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
321    Avx512(Avx512),
322    /// The x86-64-v3 instruction set on (32 and 64 bit) x86, including AVX2 and FMA.
323    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
324    Avx2(Avx2),
325    // If new variants are added, make sure to handle them in `Level::dispatch`
326    // and `dispatch!()`
327}
328
329impl Level {
330    /// Detect the available features on the current CPU, and returns the best level.
331    ///
332    /// If no SIMD instruction set is available, a scalar fallback will be used instead.
333    ///
334    /// This function requires the standard library, to use the
335    /// [`is_x86_feature_detected`](std::arch::is_x86_feature_detected)
336    /// or [`is_aarch64_feature_detected`](std::arch::is_aarch64_feature_detected).
337    /// On wasm32, this requirement does not apply, so the standard library isn't required.
338    ///
339    /// Note that in most cases, this function should only be called by end-user applications.
340    /// Libraries should instead accept a `Level` argument, probably as they are
341    /// creating their data structures, then storing the level for any computations.
342    /// Libraries which wish to abstract away SIMD usage for their common-case clients,
343    /// should make their non-`Level` entrypoint match this function's `cfg`; to instead
344    /// handle this at runtime, they can use [`try_detect`](Self::try_detect),
345    /// handling the `None` case as they deem fit (probably panicking).
346    /// This strategy avoids users of the library inadvertently using the fallback level,
347    /// even if the requisite target features are available.
348    ///
349    /// If you are on an embedded device where these macros are not supported,
350    /// you should construct the relevant variants yourself, using whatever
351    /// way your specific chip supports accessing the current level.
352    ///
353    /// This value should be passed to [`dispatch`].
354    #[cfg(any(feature = "std", target_arch = "wasm32"))]
355    #[must_use]
356    #[expect(
357        clippy::new_without_default,
358        reason = "The `Level::new()` function is not always available, and we also want to be explicit about when runtime feature detection happens"
359    )]
360    pub fn new() -> Self {
361        #[cfg(target_arch = "aarch64")]
362        if std::arch::is_aarch64_feature_detected!("neon") {
363            return unsafe { Self::Neon(Neon::new_unchecked()) };
364        }
365        #[cfg(target_arch = "wasm32")]
366        {
367            // WASM always either has the SIMD feature compiled in or not.
368            #[cfg(target_feature = "simd128")]
369            return Self::WasmSimd128(WasmSimd128::new_unchecked());
370        }
371        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
372        {
373            if x86_detects_icelake_avx512() {
374                return unsafe { Self::Avx512(Avx512::new_unchecked()) };
375            }
376
377            // Feature list sourced from `rustc --print=cfg --target x86_64-unknown-linux-gnu -C target-cpu=x86-64-v3`
378            // However, the following features are implied by avx2 and do not need to be spelled out:
379            // avx,fxsr,sse,sse2,sse3,sse4.1,sse4.2,ssse3
380            // This can be verified by running:
381            // rustc --print=cfg --target x86_64-unknown-linux-gnu -C target-feature='+avx2'
382            if std::arch::is_x86_feature_detected!("avx2")
383                && std::arch::is_x86_feature_detected!("bmi1")
384                && std::arch::is_x86_feature_detected!("bmi2")
385                && std::arch::is_x86_feature_detected!("cmpxchg16b")
386                && std::arch::is_x86_feature_detected!("f16c")
387                && std::arch::is_x86_feature_detected!("fma")
388                && std::arch::is_x86_feature_detected!("lzcnt")
389                && std::arch::is_x86_feature_detected!("movbe")
390                && std::arch::is_x86_feature_detected!("popcnt")
391                && std::arch::is_x86_feature_detected!("xsave")
392            {
393                return unsafe { Self::Avx2(Avx2::new_unchecked()) };
394            // All x86 CPUs that ever shipped with sse4.2 also have cmpxchg16b and popcnt:
395            // Intel Nehalem, AMD Bulldozer and VIA Isaiah II were the first with SSE4.2
396            // and have these extensions already.
397            } else if std::arch::is_x86_feature_detected!("sse4.2")
398                && std::arch::is_x86_feature_detected!("cmpxchg16b")
399                && std::arch::is_x86_feature_detected!("popcnt")
400            {
401                return unsafe { Self::Sse4_2(Sse4_2::new_unchecked()) };
402            }
403        }
404        #[cfg(any(
405            all(target_arch = "aarch64", not(target_feature = "neon")),
406            all(
407                any(target_arch = "x86", target_arch = "x86_64"),
408                not(all(
409                    target_feature = "sse4.2",
410                    target_feature = "cmpxchg16b",
411                    target_feature = "popcnt"
412                ))
413            ),
414            all(target_arch = "wasm32", not(target_feature = "simd128")),
415            not(any(
416                target_arch = "x86",
417                target_arch = "x86_64",
418                target_arch = "aarch64",
419                target_arch = "wasm32"
420            )),
421        ))]
422        {
423            return Self::Fallback(Fallback::new());
424        }
425        #[allow(
426            unreachable_code,
427            reason = "`is_x86_feature_detected` or equivalents will have returned `true`, or Fallback was used."
428        )]
429        {
430            unreachable!()
431        }
432    }
433
434    /// Get the target feature level suitable for this run.
435    ///
436    /// Should be used in libraries if they wish to handle the case where
437    /// target features cannot be detected at runtime.
438    /// Most users should prefer [`new`](Self::new).
439    /// This is discussed in more detail in `new`'s documentation.
440    #[allow(clippy::allow_attributes, reason = "Only needed in some cfgs.")]
441    #[allow(unreachable_code, reason = "Fallback unreachable in some cfgs.")]
442    pub fn try_detect() -> Option<Self> {
443        #[cfg(any(feature = "std", target_arch = "wasm32"))]
444        return Some(Self::new());
445        None
446    }
447
448    /// Check whether this is the `Fallback` level; that is, whether no better feature level could
449    /// be statically or dynamically detected. This is useful if there's a scalarized version of
450    /// your algorithm that runs faster if SIMD isn't supported.
451    pub fn is_fallback(self) -> bool {
452        matches!(self, Self::Fallback(_))
453    }
454
455    /// If this is a proof that Neon (or better) is available, access that instruction set.
456    ///
457    /// This method should be preferred over matching against the `Neon` variant of self,
458    /// because if Fearless SIMD gets support for an instruction set which is a superset of Neon,
459    /// this method will return the Neon token even if that "better" instruction set is available.
460    ///
461    /// This can be used in combination with the [kernel] macro to safely access level-specific
462    /// SIMD intrinsics.
463    #[cfg(target_arch = "aarch64")]
464    #[inline]
465    pub fn as_neon(self) -> Option<Neon> {
466        #[allow(
467            unreachable_patterns,
468            reason = "On machines which statically support `neon`, there is only one variant."
469        )]
470        match self {
471            Self::Neon(neon) => Some(neon),
472            _ => None,
473        }
474    }
475
476    /// If this is a proof that SIMD 128 (or better) is available, access that instruction set.
477    ///
478    /// This method should be preferred over matching against the `WasmSimd128` variant of self,
479    /// because if Fearless SIMD gets support for an instruction set which is a superset of SIMD 128,
480    /// this method will return the SIMD 128 token even if that "better" instruction set is available.
481    ///
482    /// This can be used in combination with the [kernel] macro to safely access level-specific
483    /// SIMD intrinsics.
484    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
485    #[inline]
486    pub fn as_wasm_simd128(self) -> Option<WasmSimd128> {
487        #[allow(
488            unreachable_patterns,
489            reason = "On machines which statically support `simd128`, there is only one variant."
490        )]
491        match self {
492            Self::WasmSimd128(simd128) => Some(simd128),
493            _ => None,
494        }
495    }
496
497    /// If this is a proof that x86-64-v2 feature set (or better) is available, access that
498    /// instruction set.
499    ///
500    /// See [`Sse4_2::new_unchecked`] for the exact list of CPU features this token enables.
501    ///
502    /// This method should be preferred over matching against the `Sse4_2` variant of self,
503    /// because if the CPU supports a superset of SSE4.2 (e.g. AVX2 or AVX-512),
504    /// this method will return the SSE4.2 token even if that "better" instruction set is available.
505    ///
506    /// This can be used in combination with the [kernel] macro to safely access level-specific
507    /// SIMD intrinsics.
508    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
509    #[inline]
510    pub fn as_sse4_2(self) -> Option<Sse4_2> {
511        match self {
512            // Safety: The Avx512 struct represents an Ice Lake feature set, which includes the
513            // `sse4.2`, `cmpxchg16b`, and `popcnt` features required by Sse4_2.
514            Self::Avx512(_avx512) => unsafe { Some(Sse4_2::new_unchecked()) },
515            // Safety: The Avx2 struct represents the x86-64-v3 feature set being enabled, which
516            // includes the `sse4.2`, `cmpxchg16b`, and `popcnt` features required by Sse4_2.
517            Self::Avx2(_avx) => unsafe { Some(Sse4_2::new_unchecked()) },
518            Self::Sse4_2(sse42) => Some(sse42),
519            _ => None,
520        }
521    }
522
523    /// If this is a proof that the x86-64-v3 feature set (or better) is available, access that
524    /// instruction set.
525    ///
526    /// See [`Avx2::new_unchecked`] for the exact list of CPU features this token enables.
527    ///
528    /// This method should be preferred over matching against the `Avx2` variant of self,
529    /// because if the CPU supports a superset of AVX2 (e.g. AVX-512),
530    /// this method will return the AVX2 token even if that "better" instruction set is available.
531    ///
532    /// This can be used in combination with the [kernel] macro to safely access level-specific
533    /// SIMD intrinsics.
534    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
535    #[inline]
536    pub fn as_avx2(self) -> Option<Avx2> {
537        #[allow(
538            unreachable_patterns,
539            reason = "On machines which statically support `avx2`, there is only one variant."
540        )]
541        match self {
542            // Safety: The Ice Lake AVX-512 feature set includes the x86-64-v3 features required by Avx2.
543            Self::Avx512(_avx512) => unsafe { Some(Avx2::new_unchecked()) },
544            Self::Avx2(avx2) => Some(avx2),
545            _ => None,
546        }
547    }
548
549    /// If this is a proof that the Ice Lake AVX-512 feature set is available, access that
550    /// instruction set.
551    ///
552    /// See [`Avx512::new_unchecked`] for the exact list of CPU features this token enables.
553    ///
554    /// This can be used in combination with the [kernel] macro to safely access level-specific
555    /// SIMD intrinsics.
556    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
557    #[inline]
558    pub fn as_avx512(self) -> Option<Avx512> {
559        match self {
560            Self::Avx512(avx512) => Some(avx512),
561            _ => None,
562        }
563    }
564
565    /// Get the strongest statically supported SIMD level.
566    ///
567    /// That is, if your compilation run ambiently declares that a target feature is enabled,
568    /// this method will take that into account.
569    /// In most cases, you should use [`Level::new`] or [`Level::try_detect`].
570    /// This method is mainly useful for libraries, where:
571    ///
572    /// 1) Your crate features request that you not use the standard library, i.e. doesn't enable
573    ///    your `"std"` crate feature reason (so you can't use [`Level::new`] and
574    ///    [`Level::try_detect`] returns `None`); AND
575    /// 2) Your caller does not provide a [`Level`]; AND
576    /// 3) The library doesn't want to panic when it can't find a SIMD level.
577    ///
578    /// Note that in these cases, the library should clearly inform the integrator
579    /// that it is using a fallback and so not getting optimal performance (e.g. by panicking if
580    /// `debug_assertions` are enabled, and emitting a log with the "error" level otherwise).
581    /// The messages given should also provide actionable fixes, such as pointing to the
582    /// entry-point which provides a `Level`, or your `"std"` feature.
583    ///
584    /// Note that this is unaffected by the `force-support-fallback` feature.
585    /// Instead, you should use [`Level::fallback`] if you require the fallback level.
586    pub const fn baseline() -> Self {
587        // TODO: How do we possibly test that this method works in all cases?
588        // Note that you can use the `check_targets.sh` script to at least ensure that it compiles in all reasonable cases.
589        #[cfg(not(any(
590            target_arch = "x86",
591            target_arch = "x86_64",
592            target_arch = "aarch64",
593            target_arch = "wasm32"
594        )))]
595        {
596            return Self::Fallback(Fallback::new());
597        }
598        #[cfg(target_arch = "aarch64")]
599        {
600            #[cfg(target_feature = "neon")]
601            return unsafe { Self::Neon(Neon::new_unchecked()) };
602            #[cfg(not(target_feature = "neon"))]
603            return Self::Fallback(Fallback::new());
604        }
605        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
606        {
607            #[cfg(all(
608                target_feature = "adx",
609                target_feature = "aes",
610                target_feature = "avx512bitalg",
611                target_feature = "avx512bw",
612                target_feature = "avx512cd",
613                target_feature = "avx512dq",
614                target_feature = "avx512f",
615                target_feature = "avx512ifma",
616                target_feature = "avx512vbmi",
617                target_feature = "avx512vbmi2",
618                target_feature = "avx512vl",
619                target_feature = "avx512vnni",
620                target_feature = "avx512vpopcntdq",
621                target_feature = "bmi1",
622                target_feature = "bmi2",
623                target_feature = "cmpxchg16b",
624                target_feature = "fma",
625                target_feature = "gfni",
626                target_feature = "lzcnt",
627                target_feature = "movbe",
628                target_feature = "pclmulqdq",
629                target_feature = "popcnt",
630                target_feature = "rdrand",
631                target_feature = "rdseed",
632                target_feature = "sha",
633                target_feature = "vaes",
634                target_feature = "vpclmulqdq",
635                target_feature = "xsave",
636                target_feature = "xsavec",
637                target_feature = "xsaveopt",
638                target_feature = "xsaves"
639            ))]
640            return unsafe { Self::Avx512(Avx512::new_unchecked()) };
641            #[cfg(all(
642                target_feature = "avx2",
643                target_feature = "bmi1",
644                target_feature = "bmi2",
645                target_feature = "cmpxchg16b",
646                target_feature = "f16c",
647                target_feature = "fma",
648                target_feature = "lzcnt",
649                target_feature = "movbe",
650                target_feature = "popcnt",
651                target_feature = "xsave",
652                not(all(
653                    target_feature = "adx",
654                    target_feature = "aes",
655                    target_feature = "avx512bitalg",
656                    target_feature = "avx512bw",
657                    target_feature = "avx512cd",
658                    target_feature = "avx512dq",
659                    target_feature = "avx512f",
660                    target_feature = "avx512ifma",
661                    target_feature = "avx512vbmi",
662                    target_feature = "avx512vbmi2",
663                    target_feature = "avx512vl",
664                    target_feature = "avx512vnni",
665                    target_feature = "avx512vpopcntdq",
666                    target_feature = "bmi1",
667                    target_feature = "bmi2",
668                    target_feature = "cmpxchg16b",
669                    target_feature = "fma",
670                    target_feature = "gfni",
671                    target_feature = "lzcnt",
672                    target_feature = "movbe",
673                    target_feature = "pclmulqdq",
674                    target_feature = "popcnt",
675                    target_feature = "rdrand",
676                    target_feature = "rdseed",
677                    target_feature = "sha",
678                    target_feature = "vaes",
679                    target_feature = "vpclmulqdq",
680                    target_feature = "xsave",
681                    target_feature = "xsavec",
682                    target_feature = "xsaveopt",
683                    target_feature = "xsaves"
684                ))
685            ))]
686            return unsafe { Self::Avx2(Avx2::new_unchecked()) };
687            #[cfg(all(
688                all(
689                    target_feature = "sse4.2",
690                    target_feature = "cmpxchg16b",
691                    target_feature = "popcnt"
692                ),
693                not(all(
694                    target_feature = "avx2",
695                    target_feature = "bmi1",
696                    target_feature = "bmi2",
697                    target_feature = "cmpxchg16b",
698                    target_feature = "f16c",
699                    target_feature = "fma",
700                    target_feature = "lzcnt",
701                    target_feature = "movbe",
702                    target_feature = "popcnt",
703                    target_feature = "xsave"
704                ))
705            ))]
706            return unsafe { Self::Sse4_2(Sse4_2::new_unchecked()) };
707            #[cfg(not(all(
708                target_feature = "sse4.2",
709                target_feature = "cmpxchg16b",
710                target_feature = "popcnt"
711            )))]
712            return Self::Fallback(Fallback::new());
713        }
714        #[cfg(target_arch = "wasm32")]
715        {
716            #[cfg(target_feature = "simd128")]
717            return Self::WasmSimd128(WasmSimd128::new_unchecked());
718            #[cfg(not(target_feature = "simd128"))]
719            return Self::Fallback(Fallback::new());
720        }
721    }
722
723    #[doc(hidden)]
724    #[inline]
725    pub fn __dispatch_target(self) -> Self {
726        // Dispatch compiles only the selected multiversioned backends, but public tokens can
727        // still name lower levels even when the ambient target baseline makes those backends
728        // redundant. Normalize the proof to the best dispatchable level, while leaving exact
729        // token identity available for `kernel!` and explicit token use.
730        #[cfg(feature = "force_support_fallback")]
731        #[allow(
732            irrefutable_let_patterns,
733            reason = "On targets without supported SIMD, Fallback is the only Level variant."
734        )]
735        if let Self::Fallback(fallback) = self {
736            return Self::Fallback(fallback);
737        }
738
739        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
740        {
741            #[allow(unused_variables, reason = "Unused with all cfgs active")]
742            let baseline = Self::baseline();
743
744            #[cfg(not(disable_dispatch_avx512))]
745            if let Some(avx512) = self.as_avx512().or_else(|| baseline.as_avx512()) {
746                return Self::Avx512(avx512);
747            }
748
749            #[cfg(not(disable_dispatch_avx2))]
750            if let Some(avx2) = self.as_avx2().or_else(|| baseline.as_avx2()) {
751                return Self::Avx2(avx2);
752            }
753
754            #[cfg(not(disable_dispatch_sse4_2))]
755            if let Some(sse4_2) = self.as_sse4_2().or_else(|| baseline.as_sse4_2()) {
756                return Self::Sse4_2(sse4_2);
757            }
758        }
759
760        #[cfg(target_arch = "aarch64")]
761        {
762            let baseline = Self::baseline();
763            if let Some(neon) = self.as_neon().or_else(|| baseline.as_neon()) {
764                return Self::Neon(neon);
765            }
766        }
767
768        #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
769        {
770            let baseline = Self::baseline();
771            if let Some(wasm) = self
772                .as_wasm_simd128()
773                .or_else(|| baseline.as_wasm_simd128())
774            {
775                return Self::WasmSimd128(wasm);
776            }
777        }
778
779        Self::Fallback(Fallback::new())
780    }
781
782    /// Create a scalar fallback level, which uses no SIMD instructions.
783    ///
784    /// This is primarily intended for tests; most users should prefer [`Level::new`] or [`Level::baseline`].
785    ///
786    /// Note that enabling the scalar fallback does *not* mean that the fallback branch will not
787    /// contain SIMD instructions. This is because the "ambient" compilation environment has SIMD
788    /// instructions available, which may be utilised by LLVM to auto-vectorise that path.
789    #[inline]
790    #[cfg(feature = "force_support_fallback")]
791    pub const fn fallback() -> Self {
792        Self::Fallback(Fallback::new())
793    }
794
795    /// Dispatch `f` to a context where the target features which this `Level` proves are available are [enabled].
796    ///
797    /// Most users of Fearless SIMD should prefer to use [`dispatch`] to
798    /// explicitly vectorize a function. That has a better developer experience
799    /// than an implementation of `WithSimd`, and is less likely to miss a vectorization
800    /// opportunity.
801    ///
802    /// This has two use cases:
803    /// 1) To call a manually written implementation of [`WithSimd`].
804    /// 2) To ask the compiler to auto-vectorize scalar code.
805    ///
806    /// For the second case to work, the provided function *must* be attributed with `#[inline(always)]`.
807    /// Note also that any calls that function makes to other functions will likely not be auto-vectorized,
808    /// unless they are also `#[inline(always)]`.
809    ///
810    /// [enabled]: https://doc.rust-lang.org/reference/attributes/codegen.html#the-target_feature-attribute
811    #[inline]
812    #[expect(
813        unreachable_patterns,
814        reason = "Level is `non_exhaustive`, but we are in the crate it's defined."
815    )]
816    pub fn dispatch<W: WithSimd>(self, f: W) -> W::Output {
817        dispatch!(self, simd => f.with_simd(simd))
818    }
819}
820
821#[cfg(test)]
822mod tests {
823    use crate::Level;
824
825    const fn assert_is_send_sync<T: Send + Sync>() {}
826    /// If this test compiles, we know that [`Level`] is properly `Send` and `Sync`.
827    #[test]
828    fn level_is_send_sync() {
829        assert_is_send_sync::<Level>();
830    }
831}