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