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