Skip to main content

zvidlib_core/
simd.rs

1//! The process-wide SIMD instruction-set override shared by every codec kernel.
2//!
3//! zvidlib's codecs dispatch their hot loops to runtime-detected vector kernels
4//! in many independent places, each caching its own CPU feature probe. This
5//! module is the single switch every one of them consults first; see
6//! `zvidlib::simd` for the user-facing description and the per-site report.
7
8use core::sync::atomic::{AtomicU8, Ordering};
9
10#[doc(hidden)]
11pub mod vector;
12
13/// `0` means "no override"; every other value is a [`SimdIsa::code`].
14static OVERRIDE: AtomicU8 = AtomicU8::new(0);
15
16/// Forces every SIMD-dispatched kernel in the crate onto `isa`, or restores
17/// per-site automatic detection with `None`.
18///
19/// The override reaches every dispatch family: the AV1 transform and in-loop
20/// filter kernels, AV1 motion compensation (through the default level
21/// `crate::av1_mc::McContext::new` picks up), AV1 intra prediction, the
22/// Vorbis encoder's analysis kernels, the VP8 encoder, reconstruction and
23/// decoder kernels, every VP9 decoder kernel, every HEVC engine kernel, the
24/// AV1, VP8 and VP9 output color conversion, the Vorbis decoder's synthesis
25/// kernels, and the VP9 encoder's kernels. [`SimdIsa::Scalar`] therefore
26/// genuinely reaches the scalar code path rather than merely the widest
27/// scalar-ish one.
28///
29/// An instruction set this host cannot execute is clamped to
30/// [`SimdIsa::Scalar`] rather than silently ignored, so a caller that asks for
31/// AVX2 on an aarch64 machine gets a defined, reproducible arm instead of the
32/// host's best vector kernels.
33///
34/// This is safe to call at any time and from any thread: the kernels agree
35/// bit-for-bit, so a switch that lands between two blocks of the same frame
36/// still produces the same output.
37pub fn set_override(isa: Option<SimdIsa>) {
38    let code = match isa {
39        Some(isa) if available().contains(&isa) => isa.code(),
40        Some(_) => SimdIsa::Scalar.code(),
41        None => 0,
42    };
43    OVERRIDE.store(code, Ordering::Relaxed);
44}
45
46/// The instruction set currently in force: the [`set_override`] value when one
47/// is set, otherwise [`detected`].
48#[must_use]
49pub fn active() -> SimdIsa {
50    override_isa().unwrap_or_else(detected)
51}
52
53/// The widest instruction set this host supports, ignoring any
54/// [`set_override`].
55#[must_use]
56pub fn detected() -> SimdIsa {
57    detected_isa()
58}
59
60/// Every instruction set this host can execute, always including
61/// [`SimdIsa::Scalar`].
62///
63/// Benchmarks iterate this to build one measurement arm per available
64/// instruction set, so scalar and vector timings sit side by side.
65#[must_use]
66pub fn available() -> Vec<SimdIsa> {
67    available_isas()
68}
69
70/// An instruction set the SIMD kernels can run on.
71#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
72pub enum SimdIsa {
73    /// The portable scalar reference implementation.
74    Scalar,
75    /// x86_64 SSE4.1 (128-bit, four 32-bit lanes).
76    Sse41,
77    /// x86_64 AVX2 (256-bit, eight 32-bit lanes).
78    Avx2,
79    /// aarch64 NEON (128-bit, four 32-bit lanes).
80    Neon,
81}
82
83impl SimdIsa {
84    /// A short stable name, useful in benchmark and diagnostic output.
85    #[must_use]
86    pub fn name(self) -> &'static str {
87        match self {
88            SimdIsa::Scalar => "scalar",
89            SimdIsa::Sse41 => "sse4.1",
90            SimdIsa::Avx2 => "avx2",
91            SimdIsa::Neon => "neon",
92        }
93    }
94
95    #[doc(hidden)]
96    pub fn code(self) -> u8 {
97        match self {
98            SimdIsa::Scalar => 1,
99            SimdIsa::Sse41 => 2,
100            SimdIsa::Avx2 => 3,
101            SimdIsa::Neon => 4,
102        }
103    }
104
105    #[doc(hidden)]
106    pub fn from_code(code: u8) -> Option<Self> {
107        match code {
108            1 => Some(SimdIsa::Scalar),
109            2 => Some(SimdIsa::Sse41),
110            3 => Some(SimdIsa::Avx2),
111            4 => Some(SimdIsa::Neon),
112            _ => None,
113        }
114    }
115}
116
117/// Number of samples a single vector operation covers on `isa`, or `0` when
118/// `isa` has no vector path and callers should stay scalar.
119#[must_use]
120pub fn lanes(isa: SimdIsa) -> usize {
121    match isa {
122        SimdIsa::Scalar => 0,
123        SimdIsa::Sse41 | SimdIsa::Neon => 4,
124        SimdIsa::Avx2 => 8,
125    }
126}
127
128/// The best instruction set this CPU supports, ignoring any [`set_override`].
129#[must_use]
130pub fn detected_isa() -> SimdIsa {
131    #[cfg(target_arch = "x86_64")]
132    {
133        if std::is_x86_feature_detected!("avx2") {
134            return SimdIsa::Avx2;
135        }
136        if std::is_x86_feature_detected!("sse4.1") {
137            return SimdIsa::Sse41;
138        }
139        SimdIsa::Scalar
140    }
141    #[cfg(target_arch = "aarch64")]
142    {
143        // NEON is mandatory in the aarch64 base architecture, so no runtime
144        // probe is needed (and `is_aarch64_feature_detected!` is still
145        // unstable).
146        SimdIsa::Neon
147    }
148    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
149    {
150        SimdIsa::Scalar
151    }
152}
153
154/// Every instruction set that can be exercised on this host, always including
155/// [`SimdIsa::Scalar`]. Used by the bit-exactness tests and the benchmark.
156#[must_use]
157pub fn available_isas() -> Vec<SimdIsa> {
158    #[cfg_attr(
159        not(any(target_arch = "x86_64", target_arch = "aarch64")),
160        allow(unused_mut)
161    )]
162    let mut isas = vec![SimdIsa::Scalar];
163    #[cfg(target_arch = "x86_64")]
164    {
165        if std::is_x86_feature_detected!("sse4.1") {
166            isas.push(SimdIsa::Sse41);
167        }
168        if std::is_x86_feature_detected!("avx2") {
169            isas.push(SimdIsa::Avx2);
170        }
171    }
172    #[cfg(target_arch = "aarch64")]
173    {
174        isas.push(SimdIsa::Neon);
175    }
176    isas
177}
178
179/// The active override, or `None` when detection is in charge.
180///
181/// Every dispatch site in the crate consults this *before* its own cached
182/// probe, which is what lets the override win over a `OnceLock` that has
183/// already resolved.
184#[doc(hidden)]
185#[inline]
186#[must_use]
187pub fn override_isa() -> Option<SimdIsa> {
188    SimdIsa::from_code(OVERRIDE.load(Ordering::Relaxed))
189}
190
191/// Serializes every test that pins the process-wide override.
192///
193/// The override is one global now, so tests that used to pin four independent
194/// switches (in `av1_simd`, the HEVC in-loop filter dispatcher, and here) can
195/// no longer each hold their own mutex — they would swap the instruction set
196/// out from under each other. They all take this one instead.
197#[doc(hidden)]
198pub fn test_lock() -> std::sync::MutexGuard<'static, ()> {
199    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
200    LOCK.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    use super::test_lock as lock;
208
209    #[test]
210    fn available_always_includes_scalar_and_contains_the_detected_set() {
211        let isas = available();
212        assert!(isas.contains(&SimdIsa::Scalar));
213        assert!(isas.contains(&detected()));
214    }
215
216    #[test]
217    fn override_takes_precedence_over_detection_and_clears() {
218        let _guard = lock();
219        set_override(Some(SimdIsa::Scalar));
220        assert_eq!(active(), SimdIsa::Scalar);
221        set_override(None);
222        assert_eq!(active(), detected());
223    }
224
225    #[test]
226    fn every_available_instruction_set_can_be_pinned() {
227        let _guard = lock();
228        for isa in available() {
229            set_override(Some(isa));
230            assert_eq!(active(), isa, "{}", isa.name());
231        }
232        set_override(None);
233    }
234
235    #[test]
236    fn an_unsupported_instruction_set_clamps_to_scalar() {
237        let _guard = lock();
238        let unsupported = [
239            SimdIsa::Scalar,
240            SimdIsa::Sse41,
241            SimdIsa::Avx2,
242            SimdIsa::Neon,
243        ]
244        .into_iter()
245        .find(|isa| !available().contains(isa));
246        if let Some(isa) = unsupported {
247            set_override(Some(isa));
248            assert_eq!(active(), SimdIsa::Scalar, "{}", isa.name());
249        }
250        set_override(None);
251    }
252}