Skip to main content

simdispatch/
lib.rs

1//! # simdispatch — SIMD backend selection for the FEC stack
2//!
3//! [`simdispatch`](crate) is the Level 0 crate: the single source for SIMD
4//! capability selection across the FEC ecosystem. Everything else that ships
5//! vector kernels — `fff`, `cafft`, and every future consumer — gets its
6//! backend from here.
7//!
8//! Capability proof comes from [`archmage`](https://github.com/imazen/archmage)
9//! capability tokens: a tier is "on the host" exactly when its token
10//! [`summon`](archmage::SimdToken::summon)s. There is one probe in the whole
11//! stack, and it lives here.
12//!
13//! ## The two primitives
14//!
15//! - [`Backend`] — the ladder. Variants are named after the `archmage` tier
16//!   they prove and ordered by that tier's dispatch priority (strongest
17//!   first), so the order carries capability and cannot drift from
18//!   `archmage`'s source of truth.
19//! - [`Selection`] — a consumer declares the backends it implements
20//!   ([`Selection::supports`]) and resolves the choice for the current host:
21//!   detected via `summon`, narrowed to the supported set, then adjusted by
22//!   the **downgrade-only** `SIMD_BACKEND` override. Refusing to upgrade is a
23//!   soundness property: running vector code the CPU cannot execute is
24//!   undefined behaviour, not a preference.
25//!
26//! [`backend()`] reports the process-wide resolution of the full ladder
27//! ([`Backend::ALL`]); a consumer with a narrower kernel set builds its own
28//! [`Selection`] instead.
29//!
30//! ## `SIMD_BACKEND`
31//!
32//! The one stack-wide override, replacing the per-crate `FFF_BACKEND` /
33//! `CAFFT_BACKEND` as crates migrate. Accepted values are
34//! [`Backend::name()`] values (`scalar`, `v1`, `v2`, `v3`, `v3_gfni_crypto`, `v4`, `v4x`,
35//! `neon`, `neon_aes`, `wasm128`). It is **downgrade-only**: a request for a
36//! backend the host cannot run, or one stronger than what detection found, is
37//! ignored. The `v4` and `v4x` tiers are available with the `avx512` feature.
38//! `SIMD_BACKEND=scalar` forces the whole stack to portable code —
39//! the escape hatch operators and differential testing need.
40//!
41//! ## `no_std`
42//!
43//! The crate is `#![no_std]`. Without the `std` feature there is no
44//! environment and no runtime detection, so [`backend()`] and any
45//! [`Selection::resolve`] report [`Backend::Scalar`] unconditionally —
46//! matching how the field kernels behave on a std-less build.
47//!
48//! ## Non-scope
49//!
50//! No kernels, no intrinsics, no dispatch tables, no `unsafe`. Consumers keep
51//! every `#[target_feature]` function; this crate only answers which of them
52//! is legal to call on the current host.
53
54#![no_std]
55#![deny(missing_docs)]
56#![deny(unsafe_code)]
57
58#[cfg(feature = "std")]
59extern crate std;
60
61mod backend;
62mod selection;
63
64pub use backend::{Backend, ParseBackendError};
65pub use selection::Selection;
66
67#[cfg(feature = "internals")]
68pub use selection::internals;
69
70#[cfg(feature = "std")]
71use std::sync::LazyLock;
72
73/// The process-wide resolution of the full ladder [`Backend::ALL`], narrowed
74/// to nothing, then adjusted by the `SIMD_BACKEND` override.
75#[cfg(feature = "std")]
76static BACKEND: LazyLock<Backend> = LazyLock::new(|| {
77    Selection::new("SIMD_BACKEND")
78        .supports(Backend::ALL)
79        .resolve()
80});
81
82/// The backend the full ecosystem ladder resolves to on this host, detected
83/// once per process.
84///
85/// This is the *un-narrowed* answer: the strongest [`Backend`] a host that
86/// implemented every tier could use. A consumer that implements only a subset
87/// must resolve its own [`Selection`] rather than reuse this value.
88#[cfg(feature = "std")]
89#[must_use]
90pub fn backend() -> Backend {
91    *BACKEND
92}
93
94/// The backend the full ecosystem ladder resolves to on this host.
95///
96/// Without `std` there is no runtime detection and no environment override,
97/// so this reports [`Backend::Scalar`] unconditionally.
98#[cfg(not(feature = "std"))]
99#[must_use]
100pub fn backend() -> Backend {
101    Backend::Scalar
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn backend_reports_a_member_of_the_ladder() {
110        let backend = backend();
111        assert!(
112            Backend::ALL.contains(&backend),
113            "backend() returned {backend:?}, not in the ladder"
114        );
115    }
116
117    #[cfg(not(feature = "std"))]
118    #[test]
119    fn no_std_reports_scalar() {
120        assert_eq!(backend(), Backend::Scalar);
121        let selected = Selection::new("SIMD_BACKEND")
122            .supports(Backend::ALL)
123            .resolve();
124        assert_eq!(selected, Backend::Scalar);
125    }
126
127    #[cfg(all(feature = "std", target_arch = "x86_64"))]
128    #[test]
129    fn x86_full_ladder_floor_is_v1_without_a_scalar_override() {
130        // V1 (SSE2) always summons on x86_64, so the resolved full ladder must
131        // be an x86 tier — unless `SIMD_BACKEND=scalar` forced the whole
132        // process to portable code, which is exactly the override's job.
133        let backend = backend();
134        if std::env::var("SIMD_BACKEND")
135            .ok()
136            .is_some_and(|v| v == "scalar")
137        {
138            assert_eq!(backend, Backend::Scalar);
139        } else {
140            assert!(
141                matches!(
142                    backend,
143                    Backend::V4x
144                        | Backend::V4
145                        | Backend::V3GfniCrypto
146                        | Backend::V3
147                        | Backend::V2
148                        | Backend::V1
149                ),
150                "full ladder resolved to {backend:?} on x86_64"
151            );
152        }
153    }
154
155    #[cfg(all(feature = "std", target_arch = "aarch64"))]
156    #[test]
157    fn aarch64_full_ladder_floor_is_neon_without_a_scalar_override() {
158        let backend = backend();
159        if std::env::var("SIMD_BACKEND")
160            .ok()
161            .is_some_and(|v| v == "scalar")
162        {
163            assert_eq!(backend, Backend::Scalar);
164        } else {
165            assert!(
166                matches!(backend, Backend::NeonAes | Backend::Neon),
167                "full ladder resolved to {backend:?} on aarch64"
168            );
169        }
170    }
171}